/*-------------------------------------------------------------------------
 *
 * spi.c
 *			 服务器编程接口
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/spi.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/printtup.h"
#include "access/sysattr.h"
#include "access/xact.h"
#include "catalog/heap.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/spi_priv.h"
#include "miscadmin.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"


/*
 * 这些全局变量是各种SPI函数的API的一部分
 * （一个糟糕的API选择，但现在为时已晚）。为了减少不同SPI调用者之间干扰的风险，
 * 我们在进入/退出SPI嵌套级别时保存和恢复它们。
 */
uint64		SPI_processed = 0;
SPITupleTable *SPI_tuptable = NULL;
int			SPI_result = 0;

static _SPI_connection *_SPI_stack = NULL;
static _SPI_connection *_SPI_current = NULL;
static int	_SPI_stack_depth = 0;	/* _SPI_stack的分配大小 */
static int	_SPI_connected = -1;	/* 当前栈索引 */

typedef struct SPICallbackArg
{
	const char *query;
	RawParseMode mode;
} SPICallbackArg;

static Portal fc_SPI_cursor_open_internal(const char *fc_name, SPIPlanPtr fc_plan,
									   ParamListInfo fc_paramLI, bool fc_read_only);

static void fc__SPI_prepare_plan(const char *fc_src, SPIPlanPtr fc_plan);

static void fc__SPI_prepare_oneshot_plan(const char *fc_src, SPIPlanPtr fc_plan);

static int	fc__SPI_execute_plan(SPIPlanPtr fc_plan, const SPIExecuteOptions *fc_options,
							  Snapshot fc_snapshot, Snapshot fc_crosscheck_snapshot,
							  bool fc_fire_triggers);

static ParamListInfo fc__SPI_convert_params(int fc_nargs, Oid *fc_argtypes,
										 Datum *fc_Values, const char *fc_Nulls);

static int	fc__SPI_pquery(QueryDesc *fc_queryDesc, bool fc_fire_triggers, uint64 fc_tcount);

static void fc__SPI_error_callback(void *fc_arg);

static void fc__SPI_cursor_operation(Portal fc_portal,
								  FetchDirection fc_direction, long fc_count,
								  DestReceiver *fc_dest);

static SPIPlanPtr fc__SPI_make_plan_non_temp(SPIPlanPtr fc_plan);
static SPIPlanPtr fc__SPI_save_plan(SPIPlanPtr fc_plan);

static int	fc__SPI_begin_call(bool fc_use_exec);
static int	fc__SPI_end_call(bool fc_use_exec);
static MemoryContext fc__SPI_execmem(void);
static MemoryContext fc__SPI_procmem(void);
static bool fc__SPI_checktuples(void);


/* =================== 接口函数 =================== */

int SPI_connect(void)
{
	return SPI_connect_ext(0);
}

int SPI_connect_ext(int fc_options)
{
	int			fc_newdepth;

	/* 如有必要，扩大栈 */
	if (_SPI_stack == NULL)
	{
		if (_SPI_connected != -1 || _SPI_stack_depth != 0)
			elog(ERROR, "SPI stack corrupted");
		fc_newdepth = 16;
		_SPI_stack = (_SPI_connection *)
			MemoryContextAlloc(TopMemoryContext,
							   fc_newdepth * sizeof(_SPI_connection));
		_SPI_stack_depth = fc_newdepth;
	}
	else
	{
		if (_SPI_stack_depth <= 0 || _SPI_stack_depth <= _SPI_connected)
			elog(ERROR, "SPI stack corrupted");
		if (_SPI_stack_depth == _SPI_connected + 1)
		{
			fc_newdepth = _SPI_stack_depth * 2;
			_SPI_stack = (_SPI_connection *)
				repalloc(_SPI_stack,
						 fc_newdepth * sizeof(_SPI_connection));
			_SPI_stack_depth = fc_newdepth;
		}
	}

	/* 进入新栈级别 */
	_SPI_connected++;
	Assert(_SPI_connected >= 0 && _SPI_connected < _SPI_stack_depth);

	_SPI_current = &(_SPI_stack[_SPI_connected]);
	_SPI_current->processed = 0;
	_SPI_current->tuptable = NULL;
	_SPI_current->execSubid = InvalidSubTransactionId;
	slist_init(&_SPI_current->tuptables);
	_SPI_current->procCxt = NULL;	/* 以防我们无法创建它们 */
	_SPI_current->execCxt = NULL;
	_SPI_current->connectSubid = GetCurrentSubTransactionId();
	_SPI_current->queryEnv = NULL;
	_SPI_current->atomic = (fc_options & SPI_OPT_NONATOMIC ? false : true);
	_SPI_current->internal_xact = false;
	_SPI_current->outer_processed = SPI_processed;
	_SPI_current->outer_tuptable = SPI_tuptable;
	_SPI_current->outer_result = SPI_result;

	/*
	 * 为此过程创建内存上下文
	 *
	 * 在原子上下文中（正常情况），我们使用TopTransactionContext，
	 * 否则使用PortalContext，以便它在事务边界之间存在。
	 *
	 * XXX 在所有情况下使用PortalContext作为父上下文可能更好，
	 * 但我们可能不在portal内部（考虑延迟触发执行）。也许CurTransactionContext
	 * 可以是一个选项？目前这并不重要，因为我们在AtEOSubXact_SPI()中进行显式清理；
	 * 但也请参见AtEOXact_SPI()。
	 */
	_SPI_current->procCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : PortalContext,
												  "SPI Proc",
												  ALLOCSET_DEFAULT_SIZES);
	_SPI_current->execCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : _SPI_current->procCxt,
												  "SPI Exec",
												  ALLOCSET_DEFAULT_SIZES);
	/* ... 并切换到过程的上下文 */
	_SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt);

	/*
	 * 重置API全局变量，以便当前调用者无法意外依赖于外部调用者的状态。
	 */
	SPI_processed = 0;
	SPI_tuptable = NULL;
	SPI_result = 0;

	return SPI_OK_CONNECT;
}

int SPI_finish(void)
{
	int			fc_res;

	fc_res = fc__SPI_begin_call(false);	/* 只需检查我们是否已连接 */
	if (fc_res < 0)
		return fc_res;

	/* 恢复程序调用之前的内存上下文 */
	MemoryContextSwitchTo(_SPI_current->savedcxt);

	/* 释放程序调用中使用的内存（包括tuptables） */
	MemoryContextDelete(_SPI_current->execCxt);
	_SPI_current->execCxt = NULL;
	MemoryContextDelete(_SPI_current->procCxt);
	_SPI_current->procCxt = NULL;

	/*
	 * 恢复外部API变量，特别是SPI_tuptable，它很可能
	 * 指向刚刚删除的tuptable
	 */
	SPI_processed = _SPI_current->outer_processed;
	SPI_tuptable = _SPI_current->outer_tuptable;
	SPI_result = _SPI_current->outer_result;

	/* 退出栈级别 */
	_SPI_connected--;
	if (_SPI_connected < 0)
		_SPI_current = NULL;
	else
		_SPI_current = &(_SPI_stack[_SPI_connected]);

	return SPI_OK_FINISH;
}

/*
 * SPI_start_transaction是一个无操作，仅为向后兼容而保留。
 * SPI调用者*始终*在一个事务中。
 */
void SPI_start_transaction(void)
{
}

static void fc__SPI_commit(bool fc_chain)
{
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	SavedTransactionCharacteristics fc_savetc;

	/*
	 * 如果我们处于不允许事务终止的上下文中则抱怨。
	 * （注意：这里和_SPI_rollback应该是唯一抛出ERRCODE_INVALID_TRANSACTION_TERMINATION的地方，
	 * 这样调用者可以安全地测试他们知道发生了什么。）
	 */
	if (_SPI_current->atomic)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
				 errmsg("invalid transaction termination")));

	/*
	 * 这个限制是通过基于SPI实现的PL所要求的。
	 * 它们使用子事务建立异常块，如果出现错误则应一起回滚。
	 * 终止这样一个块中的顶级事务违反了这一理念。未来的PL
	 * 实现可能对这一点有不同的看法，在这种情况下，
	 * 这个限制将需要被精炼，或者检查可能会被移出SPI进入PL。
	 * 但是请注意，下面的代码依赖于不在子事务中。
	 */
	if (IsSubTransaction())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
				 errmsg("cannot commit while a subtransaction is active")));

	if (fc_chain)
		SaveTransactionCharacteristics(&fc_savetc);

	/* 捕获在COMMIT期间发生的任何错误 */
	PG_TRY();
	{
		/* 保护当前SPI栈条目免受删除 */
		_SPI_current->internal_xact = true;

		/*
		 * 保存可能被任何PL使用的所有固定portal。
		 * 在改变事务状态之前我们必须这样做，因为这将运行
		 * 可能抛出错误的用户定义代码。
		 */
		HoldPinnedPortals();

		/* 释放与portal相关联的快照 */
		ForgetPortalSnapshots();

		/* 执行任务 */
		CommitTransactionCommand();

		/* 立即启动一个新事务 */
		StartTransactionCommand();
		if (fc_chain)
			RestoreTransactionCharacteristics(&fc_savetc);

		MemoryContextSwitchTo(fc_oldcontext);

		_SPI_current->internal_xact = false;
	}
	PG_CATCH();
	{
		ErrorData  *fc_edata;

		/* 在调用者的上下文中保存错误信息 */
		MemoryContextSwitchTo(fc_oldcontext);
		fc_edata = CopyErrorData();
		FlushErrorState();

		/*
		 * 中止失败的事务。如果这也失败了，我们就只能
		 * 将错误传播出去……我们能做的并不多。
		 */
		AbortCurrentTransaction();

		/* …然后开始一个新事务 */
		StartTransactionCommand();
		if (fc_chain)
			RestoreTransactionCharacteristics(&fc_savetc);

		MemoryContextSwitchTo(fc_oldcontext);

		_SPI_current->internal_xact = false;

		/* 现在我们已经清理了事务，重新抛出错误 */
		ReThrowError(fc_edata);
	}
	PG_END_TRY();
}

void SPI_commit(void)
{
	fc__SPI_commit(false);
}

void SPI_commit_and_chain(void)
{
	fc__SPI_commit(true);
}

static void fc__SPI_rollback(bool fc_chain)
{
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	SavedTransactionCharacteristics fc_savetc;

	/* 请参阅 _SPI_commit() 中的注释 */
	if (_SPI_current->atomic)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
				 errmsg("invalid transaction termination")));

	/* 请参阅 _SPI_commit() 中的注释 */
	if (IsSubTransaction())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
				 errmsg("cannot roll back while a subtransaction is active")));

	if (fc_chain)
		SaveTransactionCharacteristics(&fc_savetc);

	/* 捕获在 ROLLBACK 过程中发生的任何错误 */
	PG_TRY();
	{
		/* 保护当前SPI栈条目免受删除 */
		_SPI_current->internal_xact = true;

		/*
		 * 保持任何 PL 可能正在使用的固定游标。在更改事务状态之前
		 * 我们必须这样做，因为这将运行用户定义的代码，可能抛出错误，
		 * 而且无论如何在一个已经中止的事务中都无法运行。
		 */
		HoldPinnedPortals();

		/* 释放与portal相关联的快照 */
		ForgetPortalSnapshots();

		/* 执行任务 */
		AbortCurrentTransaction();

		/* 立即启动一个新事务 */
		StartTransactionCommand();
		if (fc_chain)
			RestoreTransactionCharacteristics(&fc_savetc);

		MemoryContextSwitchTo(fc_oldcontext);

		_SPI_current->internal_xact = false;
	}
	PG_CATCH();
	{
		ErrorData  *fc_edata;

		/* 在调用者的上下文中保存错误信息 */
		MemoryContextSwitchTo(fc_oldcontext);
		fc_edata = CopyErrorData();
		FlushErrorState();

		/*
		 * 再次尝试中止失败的事务。如果这也失败了，
		 * 我们将错误传播出去……我们能做的并不多。
		 */
		AbortCurrentTransaction();

		/* …然后开始一个新事务 */
		StartTransactionCommand();
		if (fc_chain)
			RestoreTransactionCharacteristics(&fc_savetc);

		MemoryContextSwitchTo(fc_oldcontext);

		_SPI_current->internal_xact = false;

		/* 现在我们已经清理了事务，重新抛出错误 */
		ReThrowError(fc_edata);
	}
	PG_END_TRY();
}

void SPI_rollback(void)
{
	fc__SPI_rollback(false);
}

void SPI_rollback_and_chain(void)
{
	fc__SPI_rollback(true);
}

/*
 * 在事务提交或中止时清理 SPI 状态。
 */
void AtEOXact_SPI(bool fc_isCommit)
{
	bool		fc_found = false;

	/*
	 * 弹出栈条目，如果找到标记为 internal_xact 的条目则停止（
	 * 那个属于 SPI_commit 或 SPI_abort 的调用方）。
	 */
	while (_SPI_connected >= 0)
	{
		_SPI_connection *fc_connection = &(_SPI_stack[_SPI_connected]);

		if (fc_connection->internal_xact)
			break;

		fc_found = true;

		/*
		 * 我们不需要显式释放过程的内存上下文，因为
		 * 当其父上下文消失时，它们会自动消失；请参阅
		 * SPI_connect_ext 中的注释。
		 */

		/*
		 * 恢复外部全局变量并弹出栈条目。与
		 * SPI_finish() 不同，我们不会冒险切换到可能
		 * 已经消失的内存上下文。
		 */
		SPI_processed = fc_connection->outer_processed;
		SPI_tuptable = fc_connection->outer_tuptable;
		SPI_result = fc_connection->outer_result;

		_SPI_connected--;
		if (_SPI_connected < 0)
			_SPI_current = NULL;
		else
			_SPI_current = &(_SPI_stack[_SPI_connected]);
	}

	/* 我们只应该在 ABORT 期间找到要弹出的条目。 */
	if (fc_found && fc_isCommit)
		ereport(WARNING,
				(errcode(ERRCODE_WARNING),
				 errmsg("transaction left non-empty SPI stack"),
				 errhint("Check for missing \"SPI_finish\" calls.")));
}

/*
 * 在子事务提交或中止时清理 SPI 状态。
 *
 * 在提交期间，不应有当前子事务未关闭的条目；
 * 如果发现任何条目，我们将发出警告。
 */
void AtEOSubXact_SPI(bool fc_isCommit, SubTransactionId fc_mySubid)
{
	bool		fc_found = false;

	while (_SPI_connected >= 0)
	{
		_SPI_connection *fc_connection = &(_SPI_stack[_SPI_connected]);

		if (fc_connection->connectSubid != fc_mySubid)
			break;				/* 下面也不应该有任何条目 */

		if (fc_connection->internal_xact)
			break;

		fc_found = true;

		/*
		 * 明确释放过程内存（请参阅 SPI_connect 中的注释）
		 */
		if (fc_connection->execCxt)
		{
			MemoryContextDelete(fc_connection->execCxt);
			fc_connection->execCxt = NULL;
		}
		if (fc_connection->procCxt)
		{
			MemoryContextDelete(fc_connection->procCxt);
			fc_connection->procCxt = NULL;
		}

		/*
		 * 恢复外部全局变量并弹出栈条目。与
		 * SPI_finish() 不同，我们不会冒险切换到可能
		 * 已经消失的内存上下文。
		 */
		SPI_processed = fc_connection->outer_processed;
		SPI_tuptable = fc_connection->outer_tuptable;
		SPI_result = fc_connection->outer_result;

		_SPI_connected--;
		if (_SPI_connected < 0)
			_SPI_current = NULL;
		else
			_SPI_current = &(_SPI_stack[_SPI_connected]);
	}

	if (fc_found && fc_isCommit)
		ereport(WARNING,
				(errcode(ERRCODE_WARNING),
				 errmsg("subtransaction left non-empty SPI stack"),
				 errhint("Check for missing \"SPI_finish\" calls.")));

	/*
	 * 如果我们正在中止一个子事务并且存在一个开放的 SPI 上下文
	 * 包围该子事务，请清理以防止内存泄漏。
	 */
	if (_SPI_current && !fc_isCommit)
	{
		slist_mutable_iter fc_siter;

		/*
		 * 如果当前执行器操作是在当前子事务中启动的,
		 * 请丢弃执行器状态（本质上，强制调用 _SPI_end_call(true)）。
		 */
		if (_SPI_current->execSubid >= fc_mySubid)
		{
			_SPI_current->execSubid = InvalidSubTransactionId;
			MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
		}

		/* 丢弃在当前子事务中创建的任何元组表 */
		slist_foreach_modify(fc_siter, &_SPI_current->tuptables)
		{
			SPITupleTable *fc_tuptable;

			fc_tuptable = slist_container(SPITupleTable, next, fc_siter.cur);
			if (fc_tuptable->subid >= fc_mySubid)
			{
				/*
				 * 如果我们在这里使用 SPI_freetuptable()，
				 * 它对元组表列表的内部搜索将使这个操作的复杂度为 O(N^2)。
				 * 相反，只需手动释放元组表。这应该与
				 * SPI_freetuptable() 的操作相匹配。
				 */
				slist_delete_current(&fc_siter);
				if (fc_tuptable == _SPI_current->tuptable)
					_SPI_current->tuptable = NULL;
				if (fc_tuptable == SPI_tuptable)
					SPI_tuptable = NULL;
				MemoryContextDelete(fc_tuptable->tuptabcxt);
			}
		}
	}
}

/*
 * 我们是在过程内部执行（即不原子 SPI 上下文）吗？
 */
bool SPI_inside_nonatomic_context(void)
{
	if (_SPI_current == NULL)
		return false;			/* 根本不在任何 SPI 上下文中 */
	/* 这些测试必须与 _SPI_commit 对于什么是原子的看法匹配： */
	if (_SPI_current->atomic)
		return false;			/* 是原子的（即函数而非过程） */
	if (IsSubTransaction())
		return false;			/* 如果在子事务内，则它是原子的 */
	return true;
}


/* 解析、计划并执行查询字符串 */
int SPI_execute(const char *fc_src, bool fc_read_only, long fc_tcount)
{
	_SPI_plan	fc_plan;
	SPIExecuteOptions fc_options;
	int			fc_res;

	if (fc_src == NULL || fc_tcount < 0)
		return SPI_ERROR_ARGUMENT;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = CURSOR_OPT_PARALLEL_OK;

	fc__SPI_prepare_oneshot_plan(fc_src, &fc_plan);

	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.read_only = fc_read_only;
	fc_options.tcount = fc_tcount;

	fc_res = fc__SPI_execute_plan(&fc_plan, &fc_options,
							InvalidSnapshot, InvalidSnapshot,
							true);

	fc__SPI_end_call(true);
	return fc_res;
}

/* SPI_execute 的过时版本 */
int SPI_exec(const char *fc_src, long fc_tcount)
{
	return SPI_execute(fc_src, false, fc_tcount);
}

/* 解析、计划并执行查询字符串，带有可扩展选项 */
int SPI_execute_extended(const char *fc_src,
					 const SPIExecuteOptions *fc_options)
{
	int			fc_res;
	_SPI_plan	fc_plan;

	if (fc_src == NULL || fc_options == NULL)
		return SPI_ERROR_ARGUMENT;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
	if (fc_options->params)
	{
		fc_plan.parserSetup = fc_options->params->parserSetup;
		fc_plan.parserSetupArg = fc_options->params->parserSetupArg;
	}

	fc__SPI_prepare_oneshot_plan(fc_src, &fc_plan);

	fc_res = fc__SPI_execute_plan(&fc_plan, fc_options,
							InvalidSnapshot, InvalidSnapshot,
							true);

	fc__SPI_end_call(true);
	return fc_res;
}

/* 执行一个先前准备好的计划 */
int SPI_execute_plan(SPIPlanPtr fc_plan, Datum *fc_Values, const char *fc_Nulls,
				 bool fc_read_only, long fc_tcount)
{
	SPIExecuteOptions fc_options;
	int			fc_res;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC || fc_tcount < 0)
		return SPI_ERROR_ARGUMENT;

	if (fc_plan->nargs > 0 && fc_Values == NULL)
		return SPI_ERROR_PARAM;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc__SPI_convert_params(fc_plan->nargs, fc_plan->argtypes,
										 fc_Values, fc_Nulls);
	fc_options.read_only = fc_read_only;
	fc_options.tcount = fc_tcount;

	fc_res = fc__SPI_execute_plan(fc_plan, &fc_options,
							InvalidSnapshot, InvalidSnapshot,
							true);

	fc__SPI_end_call(true);
	return fc_res;
}

/* SPI_execute_plan 的过时版本 */
int SPI_execp(SPIPlanPtr fc_plan, Datum *fc_Values, const char *fc_Nulls, long fc_tcount)
{
	return SPI_execute_plan(fc_plan, fc_Values, fc_Nulls, false, fc_tcount);
}

/* 执行一个先前准备好的计划 */
int SPI_execute_plan_extended(SPIPlanPtr fc_plan,
						  const SPIExecuteOptions *fc_options)
{
	int			fc_res;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC || fc_options == NULL)
		return SPI_ERROR_ARGUMENT;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	fc_res = fc__SPI_execute_plan(fc_plan, fc_options,
							InvalidSnapshot, InvalidSnapshot,
							true);

	fc__SPI_end_call(true);
	return fc_res;
}

/* 执行一个先前准备好的计划 */
int SPI_execute_plan_with_paramlist(SPIPlanPtr fc_plan, ParamListInfo fc_params,
								bool fc_read_only, long fc_tcount)
{
	SPIExecuteOptions fc_options;
	int			fc_res;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC || fc_tcount < 0)
		return SPI_ERROR_ARGUMENT;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc_params;
	fc_options.read_only = fc_read_only;
	fc_options.tcount = fc_tcount;

	fc_res = fc__SPI_execute_plan(fc_plan, &fc_options,
							InvalidSnapshot, InvalidSnapshot,
							true);

	fc__SPI_end_call(true);
	return fc_res;
}

/*
 * SPI_execute_snapshot -- 与 SPI_execute_plan 相同，不同之处在于我们允许
 * 调用者指定使用哪些快照，这些快照将被注册在这里。此外，调用者可以指定
 * AFTER 触发器应该作为外部查询的一部分排队，而不是在命令结束时立即触发。
 *
 * 目前在 spi.sgml 中没有文档记录，因为它仅用于 RI 触发器。
 *
 * 传递 snapshot == InvalidSnapshot 将选择每个查询获取新快照的正常行为。
 */
int SPI_execute_snapshot(SPIPlanPtr fc_plan,
					 Datum *fc_Values, const char *fc_Nulls,
					 Snapshot fc_snapshot, Snapshot fc_crosscheck_snapshot,
					 bool fc_read_only, bool fc_fire_triggers, long fc_tcount)
{
	SPIExecuteOptions fc_options;
	int			fc_res;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC || fc_tcount < 0)
		return SPI_ERROR_ARGUMENT;

	if (fc_plan->nargs > 0 && fc_Values == NULL)
		return SPI_ERROR_PARAM;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc__SPI_convert_params(fc_plan->nargs, fc_plan->argtypes,
										 fc_Values, fc_Nulls);
	fc_options.read_only = fc_read_only;
	fc_options.tcount = fc_tcount;

	fc_res = fc__SPI_execute_plan(fc_plan, &fc_options,
							fc_snapshot, fc_crosscheck_snapshot,
							fc_fire_triggers);

	fc__SPI_end_call(true);
	return fc_res;
}

/*
 * SPI_execute_with_args -- 计划并执行带有提供参数的查询
 *
 * 这在功能上等同于 SPI_prepare 后跟 SPI_execute_plan。
 */
int SPI_execute_with_args(const char *fc_src,
					  int fc_nargs, Oid *fc_argtypes,
					  Datum *fc_Values, const char *fc_Nulls,
					  bool fc_read_only, long fc_tcount)
{
	int			fc_res;
	_SPI_plan	fc_plan;
	ParamListInfo fc_paramLI;
	SPIExecuteOptions fc_options;

	if (fc_src == NULL || fc_nargs < 0 || fc_tcount < 0)
		return SPI_ERROR_ARGUMENT;

	if (fc_nargs > 0 && (fc_argtypes == NULL || fc_Values == NULL))
		return SPI_ERROR_PARAM;

	fc_res = fc__SPI_begin_call(true);
	if (fc_res < 0)
		return fc_res;

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
	fc_plan.nargs = fc_nargs;
	fc_plan.argtypes = fc_argtypes;
	fc_plan.parserSetup = NULL;
	fc_plan.parserSetupArg = NULL;

	fc_paramLI = fc__SPI_convert_params(fc_nargs, fc_argtypes,
								  fc_Values, fc_Nulls);

	fc__SPI_prepare_oneshot_plan(fc_src, &fc_plan);

	memset(&fc_options, 0, sizeof(fc_options));
	fc_options.params = fc_paramLI;
	fc_options.read_only = fc_read_only;
	fc_options.tcount = fc_tcount;

	fc_res = fc__SPI_execute_plan(&fc_plan, &fc_options,
							InvalidSnapshot, InvalidSnapshot,
							true);

	fc__SPI_end_call(true);
	return fc_res;
}

SPIPlanPtr SPI_prepare(const char *fc_src, int fc_nargs, Oid *fc_argtypes)
{
	return SPI_prepare_cursor(fc_src, fc_nargs, fc_argtypes, 0);
}

SPIPlanPtr SPI_prepare_cursor(const char *fc_src, int fc_nargs, Oid *fc_argtypes,
				   int fc_cursorOptions)
{
	_SPI_plan	fc_plan;
	SPIPlanPtr	fc_result;

	if (fc_src == NULL || fc_nargs < 0 || (fc_nargs > 0 && fc_argtypes == NULL))
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	SPI_result = fc__SPI_begin_call(true);
	if (SPI_result < 0)
		return NULL;

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = fc_cursorOptions;
	fc_plan.nargs = fc_nargs;
	fc_plan.argtypes = fc_argtypes;
	fc_plan.parserSetup = NULL;
	fc_plan.parserSetupArg = NULL;

	fc__SPI_prepare_plan(fc_src, &fc_plan);

	/* 将计划复制到过程上下文 */
	fc_result = fc__SPI_make_plan_non_temp(&fc_plan);

	fc__SPI_end_call(true);

	return fc_result;
}

SPIPlanPtr SPI_prepare_extended(const char *fc_src,
					 const SPIPrepareOptions *fc_options)
{
	_SPI_plan	fc_plan;
	SPIPlanPtr	fc_result;

	if (fc_src == NULL || fc_options == NULL)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	SPI_result = fc__SPI_begin_call(true);
	if (SPI_result < 0)
		return NULL;

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = fc_options->parseMode;
	fc_plan.cursor_options = fc_options->cursorOptions;
	fc_plan.nargs = 0;
	fc_plan.argtypes = NULL;
	fc_plan.parserSetup = fc_options->parserSetup;
	fc_plan.parserSetupArg = fc_options->parserSetupArg;

	fc__SPI_prepare_plan(fc_src, &fc_plan);

	/* 将计划复制到过程上下文 */
	fc_result = fc__SPI_make_plan_non_temp(&fc_plan);

	fc__SPI_end_call(true);

	return fc_result;
}

SPIPlanPtr SPI_prepare_params(const char *fc_src,
				   ParserSetupHook fc_parserSetup,
				   void *fc_parserSetupArg,
				   int fc_cursorOptions)
{
	_SPI_plan	fc_plan;
	SPIPlanPtr	fc_result;

	if (fc_src == NULL)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	SPI_result = fc__SPI_begin_call(true);
	if (SPI_result < 0)
		return NULL;

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = fc_cursorOptions;
	fc_plan.nargs = 0;
	fc_plan.argtypes = NULL;
	fc_plan.parserSetup = fc_parserSetup;
	fc_plan.parserSetupArg = fc_parserSetupArg;

	fc__SPI_prepare_plan(fc_src, &fc_plan);

	/* 将计划复制到过程上下文 */
	fc_result = fc__SPI_make_plan_non_temp(&fc_plan);

	fc__SPI_end_call(true);

	return fc_result;
}

int SPI_keepplan(SPIPlanPtr fc_plan)
{
	ListCell   *fc_lc;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC ||
		fc_plan->saved || fc_plan->oneshot)
		return SPI_ERROR_ARGUMENT;

	/*
	 * 标记为已保存，将其重新归属到 CacheMemoryContext 下，并将所有
	 * 组件 CachedPlanSources 标记为已保存。这个序列不能在过程中失败，
	 * 因此没有长期内存泄漏的风险。
	 */
	fc_plan->saved = true;
	MemoryContextSetParent(fc_plan->plancxt, CacheMemoryContext);

	foreach(fc_lc, fc_plan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc);

		SaveCachedPlan(fc_plansource);
	}

	return 0;
}

SPIPlanPtr SPI_saveplan(SPIPlanPtr fc_plan)
{
	SPIPlanPtr	fc_newplan;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	SPI_result = fc__SPI_begin_call(false);	/* 不要更改上下文 */
	if (SPI_result < 0)
		return NULL;

	fc_newplan = fc__SPI_save_plan(fc_plan);

	SPI_result = fc__SPI_end_call(false);

	return fc_newplan;
}

int SPI_freeplan(SPIPlanPtr fc_plan)
{
	ListCell   *fc_lc;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC)
		return SPI_ERROR_ARGUMENT;

	/* 释放计划缓存条目 */
	foreach(fc_lc, fc_plan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc);

		DropCachedPlan(fc_plansource);
	}

	/* 现在摆脱 _SPI_plan 和其 plancxt 中的附属数据 */
	MemoryContextDelete(fc_plan->plancxt);

	return 0;
}

HeapTuple SPI_copytuple(HeapTuple fc_tuple)
{
	MemoryContext fc_oldcxt;
	HeapTuple	fc_ctuple;

	if (fc_tuple == NULL)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	if (_SPI_current == NULL)
	{
		SPI_result = SPI_ERROR_UNCONNECTED;
		return NULL;
	}

	fc_oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);

	fc_ctuple = heap_copytuple(fc_tuple);

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_ctuple;
}

HeapTupleHeader
SPI_returntuple(HeapTuple fc_tuple, TupleDesc fc_tupdesc)
{
	MemoryContext fc_oldcxt;
	HeapTupleHeader fc_dtup;

	if (fc_tuple == NULL || fc_tupdesc == NULL)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	if (_SPI_current == NULL)
	{
		SPI_result = SPI_ERROR_UNCONNECTED;
		return NULL;
	}

	/* 对于 RECORD 结果，确保已分配了 typmod */
	if (fc_tupdesc->tdtypeid == RECORDOID &&
		fc_tupdesc->tdtypmod < 0)
		assign_record_type_typmod(fc_tupdesc);

	fc_oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);

	fc_dtup = DatumGetHeapTupleHeader(heap_copy_tuple_as_datum(fc_tuple, fc_tupdesc));

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_dtup;
}

HeapTuple SPI_modifytuple(Relation fc_rel, HeapTuple fc_tuple, int fc_natts, int *fc_attnum,
				Datum *fc_Values, const char *fc_Nulls)
{
	MemoryContext fc_oldcxt;
	HeapTuple	fc_mtuple;
	int			fc_numberOfAttributes;
	Datum	   *fc_v;
	bool	   *fc_n;
	int			fc_i;

	if (fc_rel == NULL || fc_tuple == NULL || fc_natts < 0 || fc_attnum == NULL || fc_Values == NULL)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return NULL;
	}

	if (_SPI_current == NULL)
	{
		SPI_result = SPI_ERROR_UNCONNECTED;
		return NULL;
	}

	fc_oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);

	SPI_result = 0;

	fc_numberOfAttributes = fc_rel->rd_att->natts;
	fc_v = (Datum *) palloc(fc_numberOfAttributes * sizeof(Datum));
	fc_n = (bool *) palloc(fc_numberOfAttributes * sizeof(bool));

	/* 获取旧值和空值 */
	heap_deform_tuple(fc_tuple, fc_rel->rd_att, fc_v, fc_n);

	/* 替换值和空值 */
	for (fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		if (fc_attnum[fc_i] <= 0 || fc_attnum[fc_i] > fc_numberOfAttributes)
			break;
		fc_v[fc_attnum[fc_i] - 1] = fc_Values[fc_i];
		fc_n[fc_attnum[fc_i] - 1] = (fc_Nulls && fc_Nulls[fc_i] == 'n');
	}

	if (fc_i == fc_natts)				/* 在 *attnum 中没有错误 */
	{
		fc_mtuple = heap_form_tuple(fc_rel->rd_att, fc_v, fc_n);

		/*
		 * 复制旧元组的识别信息：t_ctid, t_self 和
		 * OID（如果有的话）
		 */
		fc_mtuple->t_data->t_ctid = fc_tuple->t_data->t_ctid;
		fc_mtuple->t_self = fc_tuple->t_self;
		fc_mtuple->t_tableOid = fc_tuple->t_tableOid;
	}
	else
	{
		fc_mtuple = NULL;
		SPI_result = SPI_ERROR_NOATTRIBUTE;
	}

	pfree(fc_v);
	pfree(fc_n);

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_mtuple;
}

int SPI_fnumber(TupleDesc fc_tupdesc, const char *fc_fname)
{
	int			fc_res;
	const FormData_pg_attribute *fc_sysatt;

	for (fc_res = 0; fc_res < fc_tupdesc->natts; fc_res++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_res);

		if (namestrcmp(&fc_attr->attname, fc_fname) == 0 &&
			!fc_attr->attisdropped)
			return fc_res + 1;
	}

	fc_sysatt = SystemAttributeByName(fc_fname);
	if (fc_sysatt != NULL)
		return fc_sysatt->attnum;

	/* SPI_ERROR_NOATTRIBUTE 与所有系统列号不同 */
	return SPI_ERROR_NOATTRIBUTE;
}

char * SPI_fname(TupleDesc fc_tupdesc, int fc_fnumber)
{
	const FormData_pg_attribute *fc_att;

	SPI_result = 0;

	if (fc_fnumber > fc_tupdesc->natts || fc_fnumber == 0 ||
		fc_fnumber <= FirstLowInvalidHeapAttributeNumber)
	{
		SPI_result = SPI_ERROR_NOATTRIBUTE;
		return NULL;
	}

	if (fc_fnumber > 0)
		fc_att = TupleDescAttr(fc_tupdesc, fc_fnumber - 1);
	else
		fc_att = SystemAttributeDefinition(fc_fnumber);

	return pstrdup(NameStr(fc_att->attname));
}

char * SPI_getvalue(HeapTuple fc_tuple, TupleDesc fc_tupdesc, int fc_fnumber)
{
	Datum		fc_val;
	bool		fc_isnull;
	Oid			fc_typoid,
				fc_foutoid;
	bool		fc_typisvarlena;

	SPI_result = 0;

	if (fc_fnumber > fc_tupdesc->natts || fc_fnumber == 0 ||
		fc_fnumber <= FirstLowInvalidHeapAttributeNumber)
	{
		SPI_result = SPI_ERROR_NOATTRIBUTE;
		return NULL;
	}

	fc_val = heap_getattr(fc_tuple, fc_fnumber, fc_tupdesc, &fc_isnull);
	if (fc_isnull)
		return NULL;

	if (fc_fnumber > 0)
		fc_typoid = TupleDescAttr(fc_tupdesc, fc_fnumber - 1)->atttypid;
	else
		fc_typoid = (SystemAttributeDefinition(fc_fnumber))->atttypid;

	getTypeOutputInfo(fc_typoid, &fc_foutoid, &fc_typisvarlena);

	return OidOutputFunctionCall(fc_foutoid, fc_val);
}

Datum SPI_getbinval(HeapTuple fc_tuple, TupleDesc fc_tupdesc, int fc_fnumber, bool *fc_isnull)
{
	SPI_result = 0;

	if (fc_fnumber > fc_tupdesc->natts || fc_fnumber == 0 ||
		fc_fnumber <= FirstLowInvalidHeapAttributeNumber)
	{
		SPI_result = SPI_ERROR_NOATTRIBUTE;
		*fc_isnull = true;
		return (Datum) NULL;
	}

	return heap_getattr(fc_tuple, fc_fnumber, fc_tupdesc, fc_isnull);
}

char * SPI_gettype(TupleDesc fc_tupdesc, int fc_fnumber)
{
	Oid			fc_typoid;
	HeapTuple	fc_typeTuple;
	char	   *fc_result;

	SPI_result = 0;

	if (fc_fnumber > fc_tupdesc->natts || fc_fnumber == 0 ||
		fc_fnumber <= FirstLowInvalidHeapAttributeNumber)
	{
		SPI_result = SPI_ERROR_NOATTRIBUTE;
		return NULL;
	}

	if (fc_fnumber > 0)
		fc_typoid = TupleDescAttr(fc_tupdesc, fc_fnumber - 1)->atttypid;
	else
		fc_typoid = (SystemAttributeDefinition(fc_fnumber))->atttypid;

	fc_typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typoid));

	if (!HeapTupleIsValid(fc_typeTuple))
	{
		SPI_result = SPI_ERROR_TYPUNKNOWN;
		return NULL;
	}

	fc_result = pstrdup(NameStr(((Form_pg_type) GETSTRUCT(fc_typeTuple))->typname));
	ReleaseSysCache(fc_typeTuple);
	return fc_result;
}

/*
 * 获取列的数据类型 OID。
 *
 * 对于 typmod 和 typcollation 没有类似的东西。很少使用
 * 它们的消费者应该直接检查 TupleDesc。
 */
Oid SPI_gettypeid(TupleDesc fc_tupdesc, int fc_fnumber)
{
	SPI_result = 0;

	if (fc_fnumber > fc_tupdesc->natts || fc_fnumber == 0 ||
		fc_fnumber <= FirstLowInvalidHeapAttributeNumber)
	{
		SPI_result = SPI_ERROR_NOATTRIBUTE;
		return InvalidOid;
	}

	if (fc_fnumber > 0)
		return TupleDescAttr(fc_tupdesc, fc_fnumber - 1)->atttypid;
	else
		return (SystemAttributeDefinition(fc_fnumber))->atttypid;
}

char * SPI_getrelname(Relation fc_rel)
{
	return pstrdup(RelationGetRelationName(fc_rel));
}

char * SPI_getnspname(Relation fc_rel)
{
	return get_namespace_name(RelationGetNamespace(fc_rel));
}

void * SPI_palloc(Size fc_size)
{
	if (_SPI_current == NULL)
		elog(ERROR, "SPI_palloc called while not connected to SPI");

	return MemoryContextAlloc(_SPI_current->savedcxt, fc_size);
}

void * SPI_repalloc(void *fc_pointer, Size fc_size)
{
	/* 不再需要担心哪个上下文块在... */
	return repalloc(fc_pointer, fc_size);
}

void SPI_pfree(void *fc_pointer)
{
	/* 不再需要担心哪个上下文块在... */
	pfree(fc_pointer);
}

Datum SPI_datumTransfer(Datum fc_value, bool fc_typByVal, int fc_typLen)
{
	MemoryContext fc_oldcxt;
	Datum		fc_result;

	if (_SPI_current == NULL)
		elog(ERROR, "SPI_datumTransfer called while not connected to SPI");

	fc_oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);

	fc_result = datumTransfer(fc_value, fc_typByVal, fc_typLen);

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_result;
}

void SPI_freetuple(HeapTuple fc_tuple)
{
	/* 不再需要担心哪个上下文元组在... */
	heap_freetuple(fc_tuple);
}

void SPI_freetuptable(SPITupleTable *fc_tuptable)
{
	bool		fc_found = false;

	/* 如果为空指针则忽略调用 */
	if (fc_tuptable == NULL)
		return;

	/*
	 * 仅在最上层的 SPI 上下文中搜索匹配的元组表。
	 */
	if (_SPI_current != NULL)
	{
		slist_mutable_iter fc_siter;

		/* 在活动列表中找到元组表，然后将其移除 */
		slist_foreach_modify(fc_siter, &_SPI_current->tuptables)
		{
			SPITupleTable *fc_tt;

			fc_tt = slist_container(SPITupleTable, next, fc_siter.cur);
			if (fc_tt == fc_tuptable)
			{
				slist_delete_current(&fc_siter);
				fc_found = true;
				break;
			}
		}
	}

	/*
	 * 如果在最上层的 SPI 上下文中没有找到，则拒绝删除。
	 * 这主要是防止双重删除，但也可能防止其他错误。
	 * 由于不删除元组表的最糟后果将是临时内存泄漏，这只是一个警告。
	 */
	if (!fc_found)
	{
		elog(WARNING, "attempt to delete invalid SPITupleTable %p", fc_tuptable);
		return;
	}

	/* 为安全起见，重置可能指向元组表的全局变量 */
	if (fc_tuptable == _SPI_current->tuptable)
		_SPI_current->tuptable = NULL;
	if (fc_tuptable == SPI_tuptable)
		SPI_tuptable = NULL;

	/* 释放所有属于元组表的内存 */
	MemoryContextDelete(fc_tuptable->tuptabcxt);
}


/*
 * SPI_cursor_open()
 *
 *	将准备好的 SPI 计划作为门户打开
 */
Portal SPI_cursor_open(const char *fc_name, SPIPlanPtr fc_plan,
				Datum *fc_Values, const char *fc_Nulls,
				bool fc_read_only)
{
	Portal		fc_portal;
	ParamListInfo fc_paramLI;

	/* 在调用者的上下文中构建临时 ParamListInfo */
	fc_paramLI = fc__SPI_convert_params(fc_plan->nargs, fc_plan->argtypes,
								  fc_Values, fc_Nulls);

	fc_portal = fc_SPI_cursor_open_internal(fc_name, fc_plan, fc_paramLI, fc_read_only);

	/* 完成临时 ParamListInfo */
	if (fc_paramLI)
		pfree(fc_paramLI);

	return fc_portal;
}


/*
 * SPI_cursor_open_with_args()
 *
 * 解析和计划一个查询并将其作为门户打开。
 */
Portal SPI_cursor_open_with_args(const char *fc_name,
						  const char *fc_src,
						  int fc_nargs, Oid *fc_argtypes,
						  Datum *fc_Values, const char *fc_Nulls,
						  bool fc_read_only, int fc_cursorOptions)
{
	Portal		fc_result;
	_SPI_plan	fc_plan;
	ParamListInfo fc_paramLI;

	if (fc_src == NULL || fc_nargs < 0)
		elog(ERROR, "SPI_cursor_open_with_args called with invalid arguments");

	if (fc_nargs > 0 && (fc_argtypes == NULL || fc_Values == NULL))
		elog(ERROR, "SPI_cursor_open_with_args called with missing parameters");

	SPI_result = fc__SPI_begin_call(true);
	if (SPI_result < 0)
		elog(ERROR, "SPI_cursor_open_with_args called while not connected");

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = fc_cursorOptions;
	fc_plan.nargs = fc_nargs;
	fc_plan.argtypes = fc_argtypes;
	fc_plan.parserSetup = NULL;
	fc_plan.parserSetupArg = NULL;

	/* 在执行器上下文中构建临时 ParamListInfo */
	fc_paramLI = fc__SPI_convert_params(fc_nargs, fc_argtypes,
								  fc_Values, fc_Nulls);

	fc__SPI_prepare_plan(fc_src, &fc_plan);

	/* 我们不需要复制计划；SPI_cursor_open_internal 会这样做 */

	fc_result = fc_SPI_cursor_open_internal(fc_name, &fc_plan, fc_paramLI, fc_read_only);

	/* 并进行清理 */
	fc__SPI_end_call(true);

	return fc_result;
}


/*
 * SPI_cursor_open_with_paramlist()
 *
 *	与 SPI_cursor_open 相同，除了参数（如果有的话）作为 ParamListInfo 传递，支持动态参数集确定。
 */
Portal SPI_cursor_open_with_paramlist(const char *fc_name, SPIPlanPtr fc_plan,
							   ParamListInfo fc_params, bool fc_read_only)
{
	return fc_SPI_cursor_open_internal(fc_name, fc_plan, fc_params, fc_read_only);
}

/* 解析查询并将其打开为游标 */
Portal SPI_cursor_parse_open(const char *fc_name,
					  const char *fc_src,
					  const SPIParseOpenOptions *fc_options)
{
	Portal		fc_result;
	_SPI_plan	fc_plan;

	if (fc_src == NULL || fc_options == NULL)
		elog(ERROR, "SPI_cursor_parse_open called with invalid arguments");

	SPI_result = fc__SPI_begin_call(true);
	if (SPI_result < 0)
		elog(ERROR, "SPI_cursor_parse_open called while not connected");

	memset(&fc_plan, 0, sizeof(_SPI_plan));
	fc_plan.magic = _SPI_PLAN_MAGIC;
	fc_plan.parse_mode = RAW_PARSE_DEFAULT;
	fc_plan.cursor_options = fc_options->cursorOptions;
	if (fc_options->params)
	{
		fc_plan.parserSetup = fc_options->params->parserSetup;
		fc_plan.parserSetupArg = fc_options->params->parserSetupArg;
	}

	fc__SPI_prepare_plan(fc_src, &fc_plan);

	/* 我们不需要复制计划；SPI_cursor_open_internal 会这样做 */

	fc_result = fc_SPI_cursor_open_internal(fc_name, &fc_plan,
									  fc_options->params, fc_options->read_only);

	/* 并进行清理 */
	fc__SPI_end_call(true);

	return fc_result;
}


/*
 * SPI_cursor_open_internal()
 *
 *	SPI_cursor_open 变体的通用代码
 */
static Portal
fc_SPI_cursor_open_internal(const char *fc_name, SPIPlanPtr fc_plan,
						 ParamListInfo fc_paramLI, bool fc_read_only)
{
	CachedPlanSource *fc_plansource;
	CachedPlan *fc_cplan;
	List	   *fc_stmt_list;
	char	   *fc_query_string;
	Snapshot	fc_snapshot;
	MemoryContext fc_oldcontext;
	Portal		fc_portal;
	SPICallbackArg fc_spicallbackarg;
	ErrorContextCallback fc_spierrcontext;

	/*
	 * 检查计划是否是 Portal 代码会特殊处理为返回一个元组集的内容。
	 */
	if (!SPI_is_cursor_plan(fc_plan))
	{
		/* 尝试提供一个好的错误信息 */
		const char *fc_cmdtag;

		if (list_length(fc_plan->plancache_list) != 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
					 errmsg("cannot open multi-query plan as cursor")));
		fc_plansource = (CachedPlanSource *) linitial(fc_plan->plancache_list);
		/* 失败 SPI_is_cursor_plan() 的 SELECT 必须是 SELECT INTO */
		if (fc_plansource->commandTag == CMDTAG_SELECT)
			fc_cmdtag = "SELECT INTO";
		else
			fc_cmdtag = GetCommandTagName(fc_plansource->commandTag);
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
		/* 翻译者: %s 是 SQL 命令的名称，例如 INSERT */
				 errmsg("cannot open %s query as cursor", fc_cmdtag)));
	}

	Assert(list_length(fc_plan->plancache_list) == 1);
	fc_plansource = (CachedPlanSource *) linitial(fc_plan->plancache_list);

	/* 推送 SPI 栈 */
	if (fc__SPI_begin_call(true) < 0)
		elog(ERROR, "SPI_cursor_open called while not connected");

	/* 重置 SPI 结果（注意我们故意不触碰 lastoid） */
	SPI_processed = 0;
	SPI_tuptable = NULL;
	_SPI_current->processed = 0;
	_SPI_current->tuptable = NULL;

	/* 创建门户 */
	if (fc_name == NULL || fc_name[0] == '\0')
	{
		/* 使用一个随机且不冲突的名称 */
		fc_portal = CreateNewPortal();
	}
	else
	{
		/* 在此路径中，如果同名门户已存在则报错 */
		fc_portal = CreatePortal(fc_name, false, false);
	}

	/* 将计划的查询字符串复制到门户中 */
	fc_query_string = MemoryContextStrdup(fc_portal->portalContext,
									   fc_plansource->query_string);

	/*
	 * 为 ereport() 设置错误追踪支持，以防 GetCachedPlan 抛出错误。
	 */
	fc_spicallbackarg.query = fc_plansource->query_string;
	fc_spicallbackarg.mode = fc_plan->parse_mode;
	fc_spierrcontext.callback = fc__SPI_error_callback;
	fc_spierrcontext.arg = &fc_spicallbackarg;
	fc_spierrcontext.previous = error_context_stack;
	error_context_stack = &fc_spierrcontext;

	/*
	 * 注意：对于已保存的计划，我们不能在 GetCachedPlan 和 PortalDefineQuery 之间发生任何失败；那会导致我们的 plancache 引用计数泄漏。
	 */

	/* 如有必要，重新规划，并为门户增加计划引用计数 */
	fc_cplan = GetCachedPlan(fc_plansource, fc_paramLI, NULL, _SPI_current->queryEnv);
	fc_stmt_list = fc_cplan->stmt_list;

	if (!fc_plan->saved)
	{
		/*
		 * 我们不希望门户依赖于未保存的 CachedPlanSource，
		 * 因此必须将计划复制到门户的上下文中。这里的错误
		 * 会导致我们对计划的引用计数泄漏，但这无所谓，因为计划是未保存的，因此是临时的。
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_portal->portalContext);
		fc_stmt_list = copyObject(fc_stmt_list);
		MemoryContextSwitchTo(fc_oldcontext);
		ReleaseCachedPlan(fc_cplan, NULL);
		fc_cplan = NULL;			/* 门户不应依赖于 cplan */
	}

	/*
	 * 设置门户。
	 */
	PortalDefineQuery(fc_portal,
					  NULL,		/* 没有语句名称 */
					  fc_query_string,
					  fc_plansource->commandTag,
					  fc_stmt_list,
					  fc_cplan);

	/*
	 * 为门户设置选项。默认 SCROLL 类型的选择方式与 PerformCursorOpen 一致。
	 */
	fc_portal->cursorOptions = fc_plan->cursor_options;
	if (!(fc_portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
	{
		if (list_length(fc_stmt_list) == 1 &&
			linitial_node(PlannedStmt, fc_stmt_list)->commandType != CMD_UTILITY &&
			linitial_node(PlannedStmt, fc_stmt_list)->rowMarks == NIL &&
			ExecSupportsBackwardScan(linitial_node(PlannedStmt, fc_stmt_list)->planTree))
			fc_portal->cursorOptions |= CURSOR_OPT_SCROLL;
		else
			fc_portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
	}

	/*
	 * 不允许使用 SELECT FOR UPDATE 的 SCROLL。这与 transformDeclareCursorStmt 中的检查并不冗余，因为游标选项可能未通过那里传递。
	 */
	if (fc_portal->cursorOptions & CURSOR_OPT_SCROLL)
	{
		if (list_length(fc_stmt_list) == 1 &&
			linitial_node(PlannedStmt, fc_stmt_list)->commandType != CMD_UTILITY &&
			linitial_node(PlannedStmt, fc_stmt_list)->rowMarks != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported"),
					 errdetail("Scrollable cursors must be READ ONLY.")));
	}

	/* 使当前查询环境在执行时可供门户使用。 */
	fc_portal->queryEnv = _SPI_current->queryEnv;

	/*
	 * 如果被告知为只读，最好检查只读查询。这不能提前进行，因为我们需要查看已完成的、计划好的查询。
	 * （特别是，我们不想在 GetCachedPlan 和 PortalDefineQuery 之间进行此操作，因为在这两个步骤之间抛出错误会导致我们的 plancache 引用计数泄漏。）
	 */
	if (fc_read_only)
	{
		ListCell   *fc_lc;

		foreach(fc_lc, fc_stmt_list)
		{
			PlannedStmt *fc_pstmt = lfirst_node(PlannedStmt, fc_lc);

			if (!CommandIsReadOnly(fc_pstmt))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				/* 翻译者：%s 是一个 SQL 语句名称 */
						 errmsg("%s is not allowed in a non-volatile function",
								CreateCommandName((Node *) fc_pstmt))));
		}
	}

	/* 设置要使用的快照。 */
	if (fc_read_only)
		fc_snapshot = GetActiveSnapshot();
	else
	{
		CommandCounterIncrement();
		fc_snapshot = GetTransactionSnapshot();
	}

	/*
	 * 如果计划有参数，将它们复制到门户中。注意，这必须在重新验证计划后进行，
	 * 因为在动态参数的情况下，参数集可能在重新解析期间发生变化。
	 */
	if (fc_paramLI)
	{
		fc_oldcontext = MemoryContextSwitchTo(fc_portal->portalContext);
		fc_paramLI = copyParamList(fc_paramLI);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	/*
	 * 开始门户执行。
	 */
	PortalStart(fc_portal, fc_paramLI, 0, fc_snapshot);

	Assert(fc_portal->strategy != PORTAL_MULTI_QUERY);

	/* 弹出错误上下文栈 */
	error_context_stack = fc_spierrcontext.previous;

	/* 弹出 SPI 栈 */
	fc__SPI_end_call(true);

	/* 返回创建的门户 */
	return fc_portal;
}


/*
 * SPI_cursor_find()
 *
 *	查找现有打开游标的门户
 */
Portal SPI_cursor_find(const char *fc_name)
{
	return GetPortalByName(fc_name);
}


/*
 * SPI_cursor_fetch()
 *
 *	在游标中获取行
 */
void SPI_cursor_fetch(Portal fc_portal, bool fc_forward, long fc_count)
{
	fc__SPI_cursor_operation(fc_portal,
						  fc_forward ? FETCH_FORWARD : FETCH_BACKWARD, fc_count,
						  CreateDestReceiver(DestSPI));
	/* 我们知道，DestSPI接收器不需要调用destroy */
}


/*
 * SPI_cursor_move()
 *
 *	移动游标
 */
void SPI_cursor_move(Portal fc_portal, bool fc_forward, long fc_count)
{
	fc__SPI_cursor_operation(fc_portal,
						  fc_forward ? FETCH_FORWARD : FETCH_BACKWARD, fc_count,
						  None_Receiver);
}


/*
 * SPI_scroll_cursor_fetch()
 *
 *	在可滚动游标中获取行
 */
void SPI_scroll_cursor_fetch(Portal fc_portal, FetchDirection fc_direction, long fc_count)
{
	fc__SPI_cursor_operation(fc_portal,
						  fc_direction, fc_count,
						  CreateDestReceiver(DestSPI));
	/* 我们知道，DestSPI接收器不需要调用destroy */
}


/*
 * SPI_scroll_cursor_move()
 *
 *	在可滚动游标中移动
 */
void SPI_scroll_cursor_move(Portal fc_portal, FetchDirection fc_direction, long fc_count)
{
	fc__SPI_cursor_operation(fc_portal, fc_direction, fc_count, None_Receiver);
}


/*
 * SPI_cursor_close()
 *
 *	关闭游标
 */
void SPI_cursor_close(Portal fc_portal)
{
	if (!PortalIsValid(fc_portal))
		elog(ERROR, "invalid portal in SPI cursor operation");

	PortalDrop(fc_portal, false);
}

/*
 * 返回表示argIndex处参数类型ID的Oid。第一个
 * 参数位于索引零处。
 */
Oid SPI_getargtypeid(SPIPlanPtr fc_plan, int fc_argIndex)
{
	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC ||
		fc_argIndex < 0 || fc_argIndex >= fc_plan->nargs)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return InvalidOid;
	}
	return fc_plan->argtypes[fc_argIndex];
}

/*
 * 返回准备好的计划的参数数量。
 */
int SPI_getargcount(SPIPlanPtr fc_plan)
{
	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return -1;
	}
	return fc_plan->nargs;
}

/*
 * 如果计划包含恰好一个命令，并且该命令
 * 向调用者返回元组（例如, SELECT 或
 * INSERT ... RETURNING，但不是SELECT ... INTO），则返回true。实际上，
 * 结果指示该命令是否可以与SPI_cursor_open一起使用。
 *
 * 参数
 *	  plan: 之前使用SPI_prepare准备的计划
 */
bool SPI_is_cursor_plan(SPIPlanPtr fc_plan)
{
	CachedPlanSource *fc_plansource;

	if (fc_plan == NULL || fc_plan->magic != _SPI_PLAN_MAGIC)
	{
		SPI_result = SPI_ERROR_ARGUMENT;
		return false;
	}

	if (list_length(fc_plan->plancache_list) != 1)
	{
		SPI_result = 0;
		return false;			/* 不是严格的1个预重写命令 */
	}
	fc_plansource = (CachedPlanSource *) linitial(fc_plan->plancache_list);

	/*
	 * 我们曾经在这里强制重新验证缓存计划，但这似乎
	 * 没有必要：失效可能意味着计划返回的
	 * 元组的行类型发生了变化，但不意味着它是否能返回元组。
	 */
	SPI_result = 0;

	/* 它返回元组吗？ */
	if (fc_plansource->resultDesc)
		return true;

	return false;
}

/*
 * SPI_plan_is_valid --- 测试一个SPI计划是否
 * 当前有效（即，不标记为需要重验证）。
 *
 * 使用之前请参见CachedPlanIsValid的注释。
 */
bool SPI_plan_is_valid(SPIPlanPtr fc_plan)
{
	ListCell   *fc_lc;

	Assert(fc_plan->magic == _SPI_PLAN_MAGIC);

	foreach(fc_lc, fc_plan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc);

		if (!CachedPlanIsValid(fc_plansource))
			return false;
	}
	return true;
}

/*
 * SPI_result_code_string --- 将任何SPI返回代码转换为字符串
 *
 * 这在错误消息中通常很有用。大多数调用者可能
 * 仅传递负的（错误情况）代码，但为了通用性，我们也识别
 * 成功代码。
 */
const char * SPI_result_code_string(int fc_code)
{
	static char fc_buf[64];

	switch (fc_code)
	{
		case SPI_ERROR_CONNECT:
			return "SPI_ERROR_CONNECT";
		case SPI_ERROR_COPY:
			return "SPI_ERROR_COPY";
		case SPI_ERROR_OPUNKNOWN:
			return "SPI_ERROR_OPUNKNOWN";
		case SPI_ERROR_UNCONNECTED:
			return "SPI_ERROR_UNCONNECTED";
		case SPI_ERROR_ARGUMENT:
			return "SPI_ERROR_ARGUMENT";
		case SPI_ERROR_PARAM:
			return "SPI_ERROR_PARAM";
		case SPI_ERROR_TRANSACTION:
			return "SPI_ERROR_TRANSACTION";
		case SPI_ERROR_NOATTRIBUTE:
			return "SPI_ERROR_NOATTRIBUTE";
		case SPI_ERROR_NOOUTFUNC:
			return "SPI_ERROR_NOOUTFUNC";
		case SPI_ERROR_TYPUNKNOWN:
			return "SPI_ERROR_TYPUNKNOWN";
		case SPI_ERROR_REL_DUPLICATE:
			return "SPI_ERROR_REL_DUPLICATE";
		case SPI_ERROR_REL_NOT_FOUND:
			return "SPI_ERROR_REL_NOT_FOUND";
		case SPI_OK_CONNECT:
			return "SPI_OK_CONNECT";
		case SPI_OK_FINISH:
			return "SPI_OK_FINISH";
		case SPI_OK_FETCH:
			return "SPI_OK_FETCH";
		case SPI_OK_UTILITY:
			return "SPI_OK_UTILITY";
		case SPI_OK_SELECT:
			return "SPI_OK_SELECT";
		case SPI_OK_SELINTO:
			return "SPI_OK_SELINTO";
		case SPI_OK_INSERT:
			return "SPI_OK_INSERT";
		case SPI_OK_DELETE:
			return "SPI_OK_DELETE";
		case SPI_OK_UPDATE:
			return "SPI_OK_UPDATE";
		case SPI_OK_CURSOR:
			return "SPI_OK_CURSOR";
		case SPI_OK_INSERT_RETURNING:
			return "SPI_OK_INSERT_RETURNING";
		case SPI_OK_DELETE_RETURNING:
			return "SPI_OK_DELETE_RETURNING";
		case SPI_OK_UPDATE_RETURNING:
			return "SPI_OK_UPDATE_RETURNING";
		case SPI_OK_REWRITTEN:
			return "SPI_OK_REWRITTEN";
		case SPI_OK_REL_REGISTER:
			return "SPI_OK_REL_REGISTER";
		case SPI_OK_REL_UNREGISTER:
			return "SPI_OK_REL_UNREGISTER";
		case SPI_OK_TD_REGISTER:
			return "SPI_OK_TD_REGISTER";
		case SPI_OK_MERGE:
			return "SPI_OK_MERGE";
	}
	/* 未识别的代码 ... 返回一些有用的内容 ... */
	sprintf(fc_buf, "Unrecognized SPI code %d", fc_code);
	return fc_buf;
}

/*
 * SPI_plan_get_plan_sources --- 获取一个SPI计划的基础
 * CachedPlanSources列表。
 *
 * 注意：没有检查CachedPlanSources是否是最新的。
 *
 * 之所以导出是为了让PL/pgSQL可以使用它（这比让PL/pgSQL
 * 自己直接查看SPIPlan要好）。它没有在
 * spi.sgml中记录，因为我们宁愿不让太多地方使用这个。
 */
List * SPI_plan_get_plan_sources(SPIPlanPtr fc_plan)
{
	Assert(fc_plan->magic == _SPI_PLAN_MAGIC);
	return fc_plan->plancache_list;
}

/*
 * SPI_plan_get_cached_plan --- 获取一个SPI计划的通用CachedPlan，
 * 如果SPI计划恰好包含一个CachedPlanSource。如果不是，
 * 返回NULL。
 *
 * 该计划的引用计数被递增（并记录在CurrentResourceOwner中，
 * 如果它是一个保存的计划）。调用者负责执行ReleaseCachedPlan。
 *
 * 之所以导出是为了让PL/pgSQL可以使用它（这比让PL/pgSQL
 * 自己直接查看SPIPlan要好）。它没有在
 * spi.sgml中记录，因为我们宁愿不让太多地方使用这个。
 */
CachedPlan *
SPI_plan_get_cached_plan(SPIPlanPtr fc_plan)
{
	CachedPlanSource *fc_plansource;
	CachedPlan *fc_cplan;
	SPICallbackArg fc_spicallbackarg;
	ErrorContextCallback fc_spierrcontext;

	Assert(fc_plan->magic == _SPI_PLAN_MAGIC);

	/* 这里不能支持一次性计划 */
	if (fc_plan->oneshot)
		return NULL;

	/* 必须恰好有一个CachedPlanSource */
	if (list_length(fc_plan->plancache_list) != 1)
		return NULL;
	fc_plansource = (CachedPlanSource *) linitial(fc_plan->plancache_list);

	/* 为ereport()设置错误追踪支持 */
	fc_spicallbackarg.query = fc_plansource->query_string;
	fc_spicallbackarg.mode = fc_plan->parse_mode;
	fc_spierrcontext.callback = fc__SPI_error_callback;
	fc_spierrcontext.arg = &fc_spicallbackarg;
	fc_spierrcontext.previous = error_context_stack;
	error_context_stack = &fc_spierrcontext;

	/* 获取查询的通用计划 */
	fc_cplan = GetCachedPlan(fc_plansource, NULL,
						  fc_plan->saved ? CurrentResourceOwner : NULL,
						  _SPI_current->queryEnv);
	Assert(fc_cplan == fc_plansource->gplan);

	/* 弹出错误上下文栈 */
	error_context_stack = fc_spierrcontext.previous;

	return fc_cplan;
}


/* =================== 私有函数 =================== */

/*
 * spi_dest_startup
 *		初始化以接收从执行器发送到当前SPI过程的元组
 */
void spi_dest_startup(DestReceiver *fc_self, int fc_operation, TupleDesc fc_typeinfo)
{
	SPITupleTable *fc_tuptable;
	MemoryContext fc_oldcxt;
	MemoryContext fc_tuptabcxt;

	if (_SPI_current == NULL)
		elog(ERROR, "spi_dest_startup called while not connected to SPI");

	if (_SPI_current->tuptable != NULL)
		elog(ERROR, "improper call to spi_dest_startup");

	/* 我们将元组表上下文创建为procCxt的子项 */

	fc_oldcxt = fc__SPI_procmem();	/* 切换到过程内存上下文 */

	fc_tuptabcxt = AllocSetContextCreate(CurrentMemoryContext,
									  "SPI TupTable",
									  ALLOCSET_DEFAULT_SIZES);
	MemoryContextSwitchTo(fc_tuptabcxt);

	_SPI_current->tuptable = fc_tuptable = (SPITupleTable *)
		palloc0(sizeof(SPITupleTable));
	fc_tuptable->tuptabcxt = fc_tuptabcxt;
	fc_tuptable->subid = GetCurrentSubTransactionId();

	/*
	 * 当前元组表足够有效，可以被AtEOSubXact_SPI释放，因此将
	 * 它放到SPI上下文的元组表列表中。这将确保它不会
	 * 泄漏，即使在后面几行失败的不太可能情况下。
	 */
	slist_push_head(&_SPI_current->tuptables, &fc_tuptable->next);

	/* 设置初始分配 */
	fc_tuptable->alloced = 128;
	fc_tuptable->vals = (HeapTuple *) palloc(fc_tuptable->alloced * sizeof(HeapTuple));
	fc_tuptable->numvals = 0;
	fc_tuptable->tupdesc = CreateTupleDescCopy(fc_typeinfo);

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * spi_printtup
 *		将执行器检索到的元组存储到当前SPI过程的SPITupleTable中
 */
bool spi_printtup(TupleTableSlot *fc_slot, DestReceiver *fc_self)
{
	SPITupleTable *fc_tuptable;
	MemoryContext fc_oldcxt;

	if (_SPI_current == NULL)
		elog(ERROR, "spi_printtup called while not connected to SPI");

	fc_tuptable = _SPI_current->tuptable;
	if (fc_tuptable == NULL)
		elog(ERROR, "improper call to spi_printtup");

	fc_oldcxt = MemoryContextSwitchTo(fc_tuptable->tuptabcxt);

	if (fc_tuptable->numvals >= fc_tuptable->alloced)
	{
		/* 将指针数组的大小加倍 */
		uint64		fc_newalloced = fc_tuptable->alloced * 2;

		fc_tuptable->vals = (HeapTuple *) repalloc_huge(fc_tuptable->vals,
													 fc_newalloced * sizeof(HeapTuple));
		fc_tuptable->alloced = fc_newalloced;
	}

	fc_tuptable->vals[fc_tuptable->numvals] = ExecCopySlotHeapTuple(fc_slot);
	(fc_tuptable->numvals)++;

	MemoryContextSwitchTo(fc_oldcxt);

	return true;
}

/*
 * 静态函数
 */

/*
 * 解析和分析查询字符串。
 *
 * 进入时，plan->argtypes和plan->nargs（或者plan->parserSetup
 * 和plan->parserSetupArg）必须有效，plan->parse_mode和
 * plan->cursor_options也必须有效。
 *
 * 结果存储到*plan中（具体来说, plan->plancache_list）。
 * 请注意，结果数据都在CurrentMemoryContext或其子上下文中；
 * 实际上这意味着它在SPI执行器上下文中，我们创建的是一个
 * "临时" SPIPlan。解析过程中生成的垃圾也被留在CurrentMemoryContext中。
 */
static void fc__SPI_prepare_plan(const char *fc_src, SPIPlanPtr fc_plan)
{
	List	   *fc_raw_parsetree_list;
	List	   *fc_plancache_list;
	ListCell   *fc_list_item;
	SPICallbackArg fc_spicallbackarg;
	ErrorContextCallback fc_spierrcontext;

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_spicallbackarg.query = fc_src;
	fc_spicallbackarg.mode = fc_plan->parse_mode;
	fc_spierrcontext.callback = fc__SPI_error_callback;
	fc_spierrcontext.arg = &fc_spicallbackarg;
	fc_spierrcontext.previous = error_context_stack;
	error_context_stack = &fc_spierrcontext;

	/*
	 * 解析请求字符串为一系列原始解析树。
	 */
	fc_raw_parsetree_list = raw_parser(fc_src, fc_plan->parse_mode);

	/*
	 * 对每个原始解析树进行解析分析和规则重写，将结果存储到未保存的计划缓存条目中。
	 */
	fc_plancache_list = NIL;

	foreach(fc_list_item, fc_raw_parsetree_list)
	{
		RawStmt    *fc_parsetree = lfirst_node(RawStmt, fc_list_item);
		List	   *fc_stmt_list;
		CachedPlanSource *fc_plansource;

		/*
		 * 在我们进行解析分析之前创建 CachedPlanSource，因为它需要看到未修改的原始解析树。
		 */
		fc_plansource = CreateCachedPlan(fc_parsetree,
									  fc_src,
									  CreateCommandTag(fc_parsetree->stmt));

		/*
		 * 参数数据类型由 parserSetup 钩子驱动（如果提供），否则我们使用固定参数列表。
		 */
		if (fc_plan->parserSetup != NULL)
		{
			Assert(fc_plan->nargs == 0);
			fc_stmt_list = pg_analyze_and_rewrite_withcb(fc_parsetree,
													  fc_src,
													  fc_plan->parserSetup,
													  fc_plan->parserSetupArg,
													  _SPI_current->queryEnv);
		}
		else
		{
			fc_stmt_list = pg_analyze_and_rewrite_fixedparams(fc_parsetree,
														   fc_src,
														   fc_plan->argtypes,
														   fc_plan->nargs,
														   _SPI_current->queryEnv);
		}

		/* 完成填充CachedPlanSource */
		CompleteCachedPlan(fc_plansource,
						   fc_stmt_list,
						   NULL,
						   fc_plan->argtypes,
						   fc_plan->nargs,
						   fc_plan->parserSetup,
						   fc_plan->parserSetupArg,
						   fc_plan->cursor_options,
						   false);	/* 结果未固定 */

		fc_plancache_list = lappend(fc_plancache_list, fc_plansource);
	}

	fc_plan->plancache_list = fc_plancache_list;
	fc_plan->oneshot = false;

	/*
	 * 弹出错误上下文堆栈
	 */
	error_context_stack = fc_spierrcontext.previous;
}

/*
 * 解析，但不分析，一个查询字符串。
 *
 * 这是一个简化版的 _SPI_prepare_plan，只做初步的原始解析。它创建“单次” CachedPlanSources，
 * 仍然需要在执行前进行解析分析。
 *
 * 使用“单次”形式的 CachedPlanSource 的优势在于，我们消除了数据复制和失效开销。推迟解析分析
 * 也防止了一些原始解析树是 DDL 命令，影响后续解析树的有效性时出现的问题。这两个特性对 SPI_execute()
 * 和类似情况都是有利的。
 *
 * 结果存储在 *plan 中（具体来说是 plan->plancache_list）。
 * 请注意，结果数据都在 CurrentMemoryContext 或其子上下文中；在实践中，这意味着它在 SPI 执行器上下文中，
 * 而我们创建的是一个“临时” SPIPlan。解析过程中生成的杂项也留在 CurrentMemoryContext 中。
 */
static void fc__SPI_prepare_oneshot_plan(const char *fc_src, SPIPlanPtr fc_plan)
{
	List	   *fc_raw_parsetree_list;
	List	   *fc_plancache_list;
	ListCell   *fc_list_item;
	SPICallbackArg fc_spicallbackarg;
	ErrorContextCallback fc_spierrcontext;

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_spicallbackarg.query = fc_src;
	fc_spicallbackarg.mode = fc_plan->parse_mode;
	fc_spierrcontext.callback = fc__SPI_error_callback;
	fc_spierrcontext.arg = &fc_spicallbackarg;
	fc_spierrcontext.previous = error_context_stack;
	error_context_stack = &fc_spierrcontext;

	/*
	 * 解析请求字符串为一系列原始解析树。
	 */
	fc_raw_parsetree_list = raw_parser(fc_src, fc_plan->parse_mode);

	/*
	 * 构造计划缓存条目，但不进行解析分析。
	 */
	fc_plancache_list = NIL;

	foreach(fc_list_item, fc_raw_parsetree_list)
	{
		RawStmt    *fc_parsetree = lfirst_node(RawStmt, fc_list_item);
		CachedPlanSource *fc_plansource;

		fc_plansource = CreateOneShotCachedPlan(fc_parsetree,
											 fc_src,
											 CreateCommandTag(fc_parsetree->stmt));

		fc_plancache_list = lappend(fc_plancache_list, fc_plansource);
	}

	fc_plan->plancache_list = fc_plancache_list;
	fc_plan->oneshot = true;

	/*
	 * 弹出错误上下文堆栈
	 */
	error_context_stack = fc_spierrcontext.previous;
}

/*
 * _SPI_execute_plan：使用给定选项执行给定计划
 *
 * options 包含从外部 SPI 可访问的选项：
 * params：传递给查询的参数值
 * read_only：true 表示只读执行（无 CommandCounterIncrement）
 * allow_nonatomic：true 表示允许非原子 CALL/DO 执行
 * must_return_tuples：如果查询不返回元组则抛出错误
 * tcount：执行元组计数限制，或 0 表示没有限制
 * dest：接收输出的 DestReceiver，或 NULL 表示正常的 SPI 输出
 * owner：将用于保持计划的引用计数的 ResourceOwner；
 *		如果为 NULL，则使用 CurrentResourceOwner（对于未保存的计划忽略）
 *
 * 其他仅内部可访问的选项：
 * snapshot：要使用的查询快照，或 InvalidSnapshot 表示
 *		对每个查询获取新快照的正常行为。
 * crosscheck_snapshot：用于 RI，其它均传递 InvalidSnapshot
 * fire_triggers：true 表示在查询结束时触发 AFTER 触发器（正常情况）；
 *		false 表示任何 AFTER 触发器将在外部查询结束时推迟
 */
static int fc__SPI_execute_plan(SPIPlanPtr fc_plan, const SPIExecuteOptions *fc_options,
				  Snapshot fc_snapshot, Snapshot fc_crosscheck_snapshot,
				  bool fc_fire_triggers)
{
	int			fc_my_res = 0;
	uint64		fc_my_processed = 0;
	SPITupleTable *fc_my_tuptable = NULL;
	int			fc_res = 0;
	bool		fc_allow_nonatomic;
	bool		fc_pushed_active_snap = false;
	ResourceOwner fc_plan_owner = fc_options->owner;
	SPICallbackArg fc_spicallbackarg;
	ErrorContextCallback fc_spierrcontext;
	CachedPlan *fc_cplan = NULL;
	ListCell   *fc_lc1;

	/*
	 * 我们仅在 options->allow_nonatomic 被设置
	 * *并且* 连接时提供了 SPI_OPT_NONATOMIC 标志，并且我们
	 * 不在子事务中时允许非原子行为。后两项测试与
	 * _SPI_commit() 是否允许提交匹配；详见那里的更多评论。
	 */
	fc_allow_nonatomic = fc_options->allow_nonatomic &&
		!_SPI_current->atomic && !IsSubTransaction();

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_spicallbackarg.query = NULL;	/* 我们将在下面填充此内容 */
	fc_spicallbackarg.mode = fc_plan->parse_mode;
	fc_spierrcontext.callback = fc__SPI_error_callback;
	fc_spierrcontext.arg = &fc_spicallbackarg;
	fc_spierrcontext.previous = error_context_stack;
	error_context_stack = &fc_spierrcontext;

	/*
	 * 我们支持四种不同的快照管理行为：
	 *
	 * snapshot != InvalidSnapshot, read_only = true：完全使用给定的快照。
	 *
	 * snapshot != InvalidSnapshot, read_only = false：使用给定的快照，
	 * 在每个查询树之前通过推进其命令 ID 进行修改。
	 *
	 * snapshot == InvalidSnapshot, read_only = true：对于无需快照的查询不执行任何操作。
	 * 对于那些需要的，确保存在一个 Portal 快照；然后使用它，或使用
	 * entry-time ActiveSnapshot（如果存在并且不同）。
	 *
	 * snapshot == InvalidSnapshot, read_only = false：对于无需快照的查询不执行任何操作。
	 * 对于那些需要的，确保存在一个 Portal 快照；然后，在原子执行模式下
	 * (!allow_nonatomic) 为每个用户命令获取一个全新的快照，并在每个查询树之前推进其
	 * 命令 ID。在 allow_nonatomic 模式下，我们只是使用未修改的 Portal 快照。
	 *
	 * 在前两种情况下，我们可以对整个计划列表仅推送一次快照到栈中。
	 *
	 * 注意，snapshot != InvalidSnapshot 表示一个原子执行上下文。
	 */
	if (fc_snapshot != InvalidSnapshot)
	{
		/* 这故意测试选项字段而不是派生值 */
		Assert(!fc_options->allow_nonatomic);
		if (fc_options->read_only)
		{
			PushActiveSnapshot(fc_snapshot);
			fc_pushed_active_snap = true;
		}
		else
		{
			/* 确保我们拥有一个私有的快照副本以便进行修改 */
			PushCopiedSnapshot(fc_snapshot);
			fc_pushed_active_snap = true;
		}
	}

	/*
	 * 如果计划被保存，我们确保有一个资源所有者，否者则不需要。
	 */
	if (!fc_plan->saved)
		fc_plan_owner = NULL;
	else if (fc_plan_owner == NULL)
		fc_plan_owner = CurrentResourceOwner;

	/*
	 * 我们将 must_return_tuples 解释为“必须至少有一个查询，并且所有查询必须返回元组”。
	 * 这比 SPI_is_cursor_plan 的检查要宽松一些，但似乎并没有理由强制仅有一个查询。
	 */
	if (fc_options->must_return_tuples && fc_plan->plancache_list == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("empty query does not return tuples")));

	foreach(fc_lc1, fc_plan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc1);
		List	   *fc_stmt_list;
		ListCell   *fc_lc2;

		fc_spicallbackarg.query = fc_plansource->query_string;

		/*
		 * 如果这是一个单次计划，我们仍然需要进行解析分析。
		 */
		if (fc_plan->oneshot)
		{
			RawStmt    *fc_parsetree = fc_plansource->raw_parse_tree;
			const char *fc_src = fc_plansource->query_string;
			List	   *fc_stmt_list;

			/*
			 * 参数数据类型由 parserSetup 钩子驱动（如果提供），
			 * 否则我们使用固定参数列表。
			 */
			if (fc_parsetree == NULL)
				fc_stmt_list = NIL;
			else if (fc_plan->parserSetup != NULL)
			{
				Assert(fc_plan->nargs == 0);
				fc_stmt_list = pg_analyze_and_rewrite_withcb(fc_parsetree,
														  fc_src,
														  fc_plan->parserSetup,
														  fc_plan->parserSetupArg,
														  _SPI_current->queryEnv);
			}
			else
			{
				fc_stmt_list = pg_analyze_and_rewrite_fixedparams(fc_parsetree,
															   fc_src,
															   fc_plan->argtypes,
															   fc_plan->nargs,
															   _SPI_current->queryEnv);
			}

			/* 完成填充CachedPlanSource */
			CompleteCachedPlan(fc_plansource,
							   fc_stmt_list,
							   NULL,
							   fc_plan->argtypes,
							   fc_plan->nargs,
							   fc_plan->parserSetup,
							   fc_plan->parserSetupArg,
							   fc_plan->cursor_options,
							   false);	/* 结果未固定 */
		}

		
/*
		 * 如果被要求，查询未返回元组时进行抱怨。
		 * （重新规划无法改变这一点，因此我们可以在那之前进行检查。
		 * 然而，我们必须在解析分析之后才能检查它，所以在
		 * 单次计划的情况下，这是我们可以检查的最早时间。）
		 */
		if (fc_options->must_return_tuples && !fc_plansource->resultDesc)
		{
			/* 尝试提供一个好的错误信息 */
			const char *fc_cmdtag;

			/* 没有 resultDesc 的 SELECT 必须是 SELECT INTO */
			if (fc_plansource->commandTag == CMDTAG_SELECT)
				fc_cmdtag = "SELECT INTO";
			else
				fc_cmdtag = GetCommandTagName(fc_plansource->commandTag);
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
			/* 翻译者: %s 是 SQL 命令的名称，例如 INSERT */
					 errmsg("%s query does not return tuples", fc_cmdtag)));
		}

		/*
		 * 如有需要则重新规划，并增加计划引用计数。如果是保存的
		 * 计划，则引用计数必须由计划所有者支持。
		 */
		fc_cplan = GetCachedPlan(fc_plansource, fc_options->params,
							  fc_plan_owner, _SPI_current->queryEnv);

		fc_stmt_list = fc_cplan->stmt_list;

		/*
		 * 如果没有给定特定的快照可用，并且语句
		 * 列表需要一个快照，则进行设置。
		 */
		if (fc_snapshot == InvalidSnapshot &&
			(list_length(fc_stmt_list) > 1 ||
			 (list_length(fc_stmt_list) == 1 &&
			  PlannedStmtRequiresSnapshot(linitial_node(PlannedStmt,
														fc_stmt_list)))))
		{
			/*
			 * 首先，确保有一个 Portal 级别的快照。这样可以填充
			 * 快照堆栈，以防之前的操作是 COMMIT
			 * 或 ROLLBACK 在过程或 DO 块内。（我们不能更早地放回
			 * Portal 快照，否则会破坏在 COMMIT 后紧接执行
			 * SET 或 LOCK 的情况。）每个语句列表检查一次即可，
			 * 因为 COMMIT/ROLLBACK/CALL/DO 不能出现在多个语句列表内。
			 */
			EnsurePortalSnapshotExists();

			/*
			 * 在默认的非只读情况下，获取一个新的每语句列表
			 * 快照，替换之前周期中推送的任何快照。
			 * 但在进行非原子执行时跳过（在这种情况下我们完全依赖
			 * Portal 快照）。
			 */
			if (!fc_options->read_only && !fc_allow_nonatomic)
			{
				if (fc_pushed_active_snap)
					PopActiveSnapshot();
				PushActiveSnapshot(GetTransactionSnapshot());
				fc_pushed_active_snap = true;
			}
		}

		foreach(fc_lc2, fc_stmt_list)
		{
			PlannedStmt *fc_stmt = lfirst_node(PlannedStmt, fc_lc2);
			bool		fc_canSetTag = fc_stmt->canSetTag;
			DestReceiver *fc_dest;

			/*
			 * 重置输出状态。（注意，如果使用非 SPI 接收器，
			 * _SPI_current->processed 将保持为零，我们将
			 * 向调用者报告这个值。在这种情况下，计数元组的工作由接收器来负责。）
			 */
			_SPI_current->processed = 0;
			_SPI_current->tuptable = NULL;

			/* 检查不支持的情况。 */
			if (fc_stmt->utilityStmt)
			{
				if (IsA(fc_stmt->utilityStmt, CopyStmt))
				{
					CopyStmt   *fc_cstmt = (CopyStmt *) fc_stmt->utilityStmt;

					if (fc_cstmt->filename == NULL)
					{
						fc_my_res = SPI_ERROR_COPY;
						goto fail;
					}
				}
				else if (IsA(fc_stmt->utilityStmt, TransactionStmt))
				{
					fc_my_res = SPI_ERROR_TRANSACTION;
					goto fail;
				}
			}

			if (fc_options->read_only && !CommandIsReadOnly(fc_stmt))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				/* 翻译者：%s 是一个 SQL 语句名称 */
						 errmsg("%s is not allowed in a non-volatile function",
								CreateCommandName((Node *) fc_stmt))));

			/*
			 * 如果不是只读模式，在每个命令之前提前命令计数器并更新快照。
			 * （但是如果快照不在我们的控制之下，则跳过。）
			 */
			if (!fc_options->read_only && fc_pushed_active_snap)
			{
				CommandCounterIncrement();
				UpdateActiveSnapshotCommandId();
			}

			/*
			 * 选择合适的元组接收器。来自非 canSetTag
			 * 子查询的输出总是转到垃圾桶。
			 */
			if (!fc_canSetTag)
				fc_dest = CreateDestReceiver(DestNone);
			else if (fc_options->dest)
				fc_dest = fc_options->dest;
			else
				fc_dest = CreateDestReceiver(DestSPI);

			if (fc_stmt->utilityStmt == NULL)
			{
				QueryDesc  *fc_qdesc;
				Snapshot	fc_snap;

				if (ActiveSnapshotSet())
					fc_snap = GetActiveSnapshot();
				else
					fc_snap = InvalidSnapshot;

				fc_qdesc = CreateQueryDesc(fc_stmt,
										fc_plansource->query_string,
										fc_snap, fc_crosscheck_snapshot,
										fc_dest,
										fc_options->params,
										_SPI_current->queryEnv,
										0);
				fc_res = fc__SPI_pquery(fc_qdesc, fc_fire_triggers,
								  fc_canSetTag ? fc_options->tcount : 0);
				FreeQueryDesc(fc_qdesc);
			}
			else
			{
				ProcessUtilityContext fc_context;
				QueryCompletion fc_qc;

				/*
				 * 如果我们不允许非原子操作，则告知
				 * ProcessUtility 这是一个原子执行上下文。
				 */
				if (fc_allow_nonatomic)
					fc_context = PROCESS_UTILITY_QUERY_NONATOMIC;
				else
					fc_context = PROCESS_UTILITY_QUERY;

				InitializeQueryCompletion(&fc_qc);
				ProcessUtility(fc_stmt,
							   fc_plansource->query_string,
							   true,	/* 保护计划缓存的节点树 */
							   fc_context,
							   fc_options->params,
							   _SPI_current->queryEnv,
							   fc_dest,
							   &fc_qc);

				/* 如果语句返回元组，则更新 "processed" */
				if (_SPI_current->tuptable)
					_SPI_current->processed = _SPI_current->tuptable->numvals;

				fc_res = SPI_OK_UTILITY;

				/*
				 * 一些工具语句返回行计数，即使元组并未返回给调用者。
				 */
				if (IsA(fc_stmt->utilityStmt, CreateTableAsStmt))
				{
					CreateTableAsStmt *fc_ctastmt = (CreateTableAsStmt *) fc_stmt->utilityStmt;

					if (fc_qc.commandTag == CMDTAG_SELECT)
						_SPI_current->processed = fc_qc.nprocessed;
					else
					{
						/*
						 * 必须是一个没有执行任何操作的 IF NOT EXISTS，或者是
						 * CREATE ... WITH NO DATA。
						 */
						Assert(fc_ctastmt->if_not_exists ||
							   fc_ctastmt->into->skipData);
						_SPI_current->processed = 0;
					}

					/*
					 * 由于历史原因，如果 CREATE TABLE AS 被表示为
					 * SELECT INTO，返回特殊的返回代码。
					 */
					if (fc_ctastmt->is_select_into)
						fc_res = SPI_OK_SELINTO;
				}
				else if (IsA(fc_stmt->utilityStmt, CopyStmt))
				{
					Assert(fc_qc.commandTag == CMDTAG_COPY);
					_SPI_current->processed = fc_qc.nprocessed;
				}
			}

			/*
			 * 最后一个 canSetTag 查询设置返回给调用者的状态值。
			 * 注意释放任何未返回的 tuptables，以
			 * 避免事务内存泄漏。
			 */
			if (fc_canSetTag)
			{
				fc_my_processed = _SPI_current->processed;
				SPI_freetuptable(fc_my_tuptable);
				fc_my_tuptable = _SPI_current->tuptable;
				fc_my_res = fc_res;
			}
			else
			{
				SPI_freetuptable(_SPI_current->tuptable);
				_SPI_current->tuptable = NULL;
			}

			/*
			 * 我们不对接收者发出销毁调用。
			 * SPI 和 None 接收者反正会忽略它，而如果调用者
			 * 提供了一个接收者，销毁它不是我们的职责。
			 */

			if (fc_res < 0)
			{
				fc_my_res = fc_res;
				goto fail;
			}
		}

		/* 处理完这个计划，因此释放引用计数 */
		ReleaseCachedPlan(fc_cplan, fc_plan_owner);
		fc_cplan = NULL;

		/*
		 * 如果不是只读模式，在最后一个
		 * 命令之后推进命令计数器。这确保其效果是可见的，以防它是
		 * 会影响下一个 CachedPlanSource 的 DDL。
		 */
		if (!fc_options->read_only)
			CommandCounterIncrement();
	}

fail:

	/* 如果我们推送了快照，则从堆栈中弹出它 */
	if (fc_pushed_active_snap)
		PopActiveSnapshot();

	/* 如果有缓存计划引用计数，我们不再需要它 */
	if (fc_cplan)
		ReleaseCachedPlan(fc_cplan, fc_plan_owner);

	/*
	 * 弹出错误上下文堆栈
	 */
	error_context_stack = fc_spierrcontext.previous;

	/* 为调用者保存结果 */
	SPI_processed = fc_my_processed;
	SPI_tuptable = fc_my_tuptable;

	/* tuptable 现在由调用者负责，而不是 SPI */
	_SPI_current->tuptable = NULL;

	/*
	 * 如果没有查询具有 canSetTag，则返回 SPI_OK_REWRITTEN。在
	 * 8.4 之前，我们返回最后一个查询的结果代码，但不返回其辅助
	 * 结果，但这会导致混淆。
	 */
	if (fc_my_res == 0)
		fc_my_res = SPI_OK_REWRITTEN;

	return fc_my_res;
}

/*
 * 将查询参数的数组转换为规划器和执行器所需的形式
 */
static ParamListInfo fc__SPI_convert_params(int fc_nargs, Oid *fc_argtypes,
					Datum *fc_Values, const char *fc_Nulls)
{
	ParamListInfo fc_paramLI;

	if (fc_nargs > 0)
	{
		fc_paramLI = makeParamList(fc_nargs);

		for (int fc_i = 0; fc_i < fc_nargs; fc_i++)
		{
			ParamExternData *fc_prm = &fc_paramLI->params[fc_i];

			fc_prm->value = fc_Values[fc_i];
			fc_prm->isnull = (fc_Nulls && fc_Nulls[fc_i] == 'n');
			fc_prm->pflags = PARAM_FLAG_CONST;
			fc_prm->ptype = fc_argtypes[fc_i];
		}
	}
	else
		fc_paramLI = NULL;
	return fc_paramLI;
}

static int fc__SPI_pquery(QueryDesc *fc_queryDesc, bool fc_fire_triggers, uint64 fc_tcount)
{
	int			fc_operation = fc_queryDesc->operation;
	int			fc_eflags;
	int			fc_res;

	switch (fc_operation)
	{
		case CMD_SELECT:
			if (fc_queryDesc->dest->mydest == DestNone)
			{
				/* 如果我们丢弃结果，则不要返回 SPI_OK_SELECT */
				fc_res = SPI_OK_UTILITY;
			}
			else
				fc_res = SPI_OK_SELECT;
			break;
		case CMD_INSERT:
			if (fc_queryDesc->plannedstmt->hasReturning)
				fc_res = SPI_OK_INSERT_RETURNING;
			else
				fc_res = SPI_OK_INSERT;
			break;
		case CMD_DELETE:
			if (fc_queryDesc->plannedstmt->hasReturning)
				fc_res = SPI_OK_DELETE_RETURNING;
			else
				fc_res = SPI_OK_DELETE;
			break;
		case CMD_UPDATE:
			if (fc_queryDesc->plannedstmt->hasReturning)
				fc_res = SPI_OK_UPDATE_RETURNING;
			else
				fc_res = SPI_OK_UPDATE;
			break;
		case CMD_MERGE:
			fc_res = SPI_OK_MERGE;
			break;
		default:
			return SPI_ERROR_OPUNKNOWN;
	}

#ifdef SPI_EXECUTOR_STATS
	if (ShowExecutorStats)
		ResetUsage();
#endif

	/* 选择执行选项 */
	if (fc_fire_triggers)
		fc_eflags = 0;				/* 默认完成标志 */
	else
		fc_eflags = EXEC_FLAG_SKIP_TRIGGERS;

	ExecutorStart(fc_queryDesc, fc_eflags);

	ExecutorRun(fc_queryDesc, ForwardScanDirection, fc_tcount, true);

	_SPI_current->processed = fc_queryDesc->estate->es_processed;

	if ((fc_res == SPI_OK_SELECT || fc_queryDesc->plannedstmt->hasReturning) &&
		fc_queryDesc->dest->mydest == DestSPI)
	{
		if (fc__SPI_checktuples())
			elog(ERROR, "consistency check on SPI tuple count failed");
	}

	ExecutorFinish(fc_queryDesc);
	ExecutorEnd(fc_queryDesc);
	/* FreeQueryDesc 由调用者完成 */

#ifdef SPI_EXECUTOR_STATS
	if (ShowExecutorStats)
		ShowUsage("SPI EXECUTOR STATS");
#endif

	return fc_res;
}

/*
 * _SPI_error_callback
 *
 * 当通过 SPI 调用的查询失败时添加上下文信息
 */
static void fc__SPI_error_callback(void *fc_arg)
{
	SPICallbackArg *fc_carg = (SPICallbackArg *) fc_arg;
	const char *fc_query = fc_carg->query;
	int			fc_syntaxerrposition;

	if (fc_query == NULL)			/* 以防参数尚未设置 */
		return;

	/*
	 * 如果存在语法错误位置，将其转换为内部语法错误；
	 * 否则将查询视为上下文栈的一项。
	 */
	fc_syntaxerrposition = geterrposition();
	if (fc_syntaxerrposition > 0)
	{
		errposition(0);
		internalerrposition(fc_syntaxerrposition);
		internalerrquery(fc_query);
	}
	else
	{
		/* 使用解析模式来决定如何描述查询 */
		switch (fc_carg->mode)
		{
			case RAW_PARSE_PLPGSQL_EXPR:
				errcontext("SQL expression \"%s\"", fc_query);
				break;
			case RAW_PARSE_PLPGSQL_ASSIGN1:
			case RAW_PARSE_PLPGSQL_ASSIGN2:
			case RAW_PARSE_PLPGSQL_ASSIGN3:
				errcontext("PL/pgSQL assignment \"%s\"", fc_query);
				break;
			default:
				errcontext("SQL statement \"%s\"", fc_query);
				break;
		}
	}
}

/*
 * _SPI_cursor_operation()
 *
 * 在游标中执行 FETCH 或 MOVE
 */
static void fc__SPI_cursor_operation(Portal fc_portal, FetchDirection fc_direction, long fc_count,
					  DestReceiver *fc_dest)
{
	uint64		fc_nfetched;

	/* 检查门户是否合法 */
	if (!PortalIsValid(fc_portal))
		elog(ERROR, "invalid portal in SPI cursor operation");

	/* 推送 SPI 栈 */
	if (fc__SPI_begin_call(true) < 0)
		elog(ERROR, "SPI cursor operation called while not connected");

	/* 重置 SPI 结果（请注意，我们故意不触碰 lastoid） */
	SPI_processed = 0;
	SPI_tuptable = NULL;
	_SPI_current->processed = 0;
	_SPI_current->tuptable = NULL;

	/* 运行游标 */
	fc_nfetched = PortalRunFetch(fc_portal,
							  fc_direction,
							  fc_count,
							  fc_dest);

	/*
	 * 不要想把这个存储与前面的函数调用合并。如果
	 * 门户包含使用 SPI 的函数调用，那么 _SPI_stack 在门户运行时
	 * 可能会移动。当控制返回时，_SPI_current 将指向正确的栈条目...
	 * 但指针可能与之前不同。因此，我们必须确保在函数调用完成后
	 * 重新获取指针。
	 */
	_SPI_current->processed = fc_nfetched;

	if (fc_dest->mydest == DestSPI && fc__SPI_checktuples())
		elog(ERROR, "consistency check on SPI tuple count failed");

	/* 将结果放置在调用者可以访问的地方 */
	SPI_processed = _SPI_current->processed;
	SPI_tuptable = _SPI_current->tuptable;

	/* tuptable 现在由调用者负责，而不是 SPI */
	_SPI_current->tuptable = NULL;

	/* 弹出 SPI 栈 */
	fc__SPI_end_call(true);
}


static MemoryContext fc__SPI_execmem(void)
{
	return MemoryContextSwitchTo(_SPI_current->execCxt);
}

static MemoryContext fc__SPI_procmem(void)
{
	return MemoryContextSwitchTo(_SPI_current->procCxt);
}

/*
 * _SPI_begin_call: 在连接的过程内开始 SPI 操作
 *
 * use_exec 为 true 如果我们打算在此次 SPI 操作中利用过程的 execCxt。
 * 我们将切换到该上下文，并安排在 _SPI_end_call 或发生错误时进行清理。
 */
static int fc__SPI_begin_call(bool fc_use_exec)
{
	if (_SPI_current == NULL)
		return SPI_ERROR_UNCONNECTED;

	if (fc_use_exec)
	{
		/* 记住执行器操作开始的时间 */
		_SPI_current->execSubid = GetCurrentSubTransactionId();
		/* 切换到执行器内存上下文 */
		fc__SPI_execmem();
	}

	return 0;
}

/*
 * _SPI_end_call: 在连接的过程内结束 SPI 操作
 *
 * use_exec 必须与上一个 _SPI_begin_call 中的相同
 *
 * 注意：目前没有失败返回情况，因此调用者不检查
 */
static int fc__SPI_end_call(bool fc_use_exec)
{
	if (fc_use_exec)
	{
		/* 切换到过程内存上下文 */
		fc__SPI_procmem();
		/* 标记执行器上下文不再使用 */
		_SPI_current->execSubid = InvalidSubTransactionId;
		/* 和释放执行器内存 */
		MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
	}

	return 0;
}

static bool fc__SPI_checktuples(void)
{
	uint64		fc_processed = _SPI_current->processed;
	SPITupleTable *fc_tuptable = _SPI_current->tuptable;
	bool		fc_failed = false;

	if (fc_tuptable == NULL)		/* spi_dest_startup 未被调用 */
		fc_failed = true;
	else if (fc_processed != fc_tuptable->numvals)
		fc_failed = true;

	return fc_failed;
}

/*
 * 将“临时”SPIPlan转换为“未保存”的计划。
 *
 * 传入的_SPI_plan结构在堆栈上，所有其附属数据
 * 在当前SPI执行上下文中或以下。将计划复制到
 * SPI过程上下文中，以便它在_SPI_end_call()中存活。为了最小化
 * 数据复制，这会以破坏性方式修改输入计划，通过将
 * plancache条目从中取出并将其重新父级到新的SPIPlan。
 */
static SPIPlanPtr fc__SPI_make_plan_non_temp(SPIPlanPtr fc_plan)
{
	SPIPlanPtr	fc_newplan;
	MemoryContext fc_parentcxt = _SPI_current->procCxt;
	MemoryContext fc_plancxt;
	MemoryContext fc_oldcxt;
	ListCell   *fc_lc;

	/* 断言输入是一个临时SPIPlan */
	Assert(fc_plan->magic == _SPI_PLAN_MAGIC);
	Assert(fc_plan->plancxt == NULL);
	/* 一次性计划无法被保存 */
	Assert(!fc_plan->oneshot);

	/*
	 * 为计划创建一个内存上下文，在过程上下文下方。
	 * 我们不希望该计划非常大。
	 */
	fc_plancxt = AllocSetContextCreate(fc_parentcxt,
									"SPI Plan",
									ALLOCSET_SMALL_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_plancxt);

	/* 将_SPI_plan结构和附属数据复制到新的上下文中 */
	fc_newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
	fc_newplan->magic = _SPI_PLAN_MAGIC;
	fc_newplan->plancxt = fc_plancxt;
	fc_newplan->parse_mode = fc_plan->parse_mode;
	fc_newplan->cursor_options = fc_plan->cursor_options;
	fc_newplan->nargs = fc_plan->nargs;
	if (fc_plan->nargs > 0)
	{
		fc_newplan->argtypes = (Oid *) palloc(fc_plan->nargs * sizeof(Oid));
		memcpy(fc_newplan->argtypes, fc_plan->argtypes, fc_plan->nargs * sizeof(Oid));
	}
	else
		fc_newplan->argtypes = NULL;
	fc_newplan->parserSetup = fc_plan->parserSetup;
	fc_newplan->parserSetupArg = fc_plan->parserSetupArg;

	/*
	 * 将所有CachedPlanSources重新父级到过程上下文中。
	 * 理论上，由于pallocs，这可能会在中途失败，但我们不
	 * 太在意，因为过程上下文和执行上下文
	 * 会在错误时消失。
	 */
	foreach(fc_lc, fc_plan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc);

		CachedPlanSetParentContext(fc_plansource, fc_parentcxt);

		/* 构建新列表，列表单元在plancxt中 */
		fc_newplan->plancache_list = lappend(fc_newplan->plancache_list, fc_plansource);
	}

	MemoryContextSwitchTo(fc_oldcxt);

	/* 为了安全，从临时计划中解除CachedPlanSources的链接 */
	fc_plan->plancache_list = NIL;

	return fc_newplan;
}

/*
 * 制作给定计划的“保存”副本。
 */
static SPIPlanPtr fc__SPI_save_plan(SPIPlanPtr fc_plan)
{
	SPIPlanPtr	fc_newplan;
	MemoryContext fc_plancxt;
	MemoryContext fc_oldcxt;
	ListCell   *fc_lc;

	/* 一次性计划无法被保存 */
	Assert(!fc_plan->oneshot);

	/*
	 * 为计划创建一个内存上下文。我们不希望该计划
	 * 非常大，因此使用小于默认的分配参数。它是一个
	 * 瞬态上下文，直到我们完成所有内容的复制。
	 */
	fc_plancxt = AllocSetContextCreate(CurrentMemoryContext,
									"SPI Plan",
									ALLOCSET_SMALL_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_plancxt);

	/* 将SPI计划复制到自己的上下文中 */
	fc_newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
	fc_newplan->magic = _SPI_PLAN_MAGIC;
	fc_newplan->plancxt = fc_plancxt;
	fc_newplan->parse_mode = fc_plan->parse_mode;
	fc_newplan->cursor_options = fc_plan->cursor_options;
	fc_newplan->nargs = fc_plan->nargs;
	if (fc_plan->nargs > 0)
	{
		fc_newplan->argtypes = (Oid *) palloc(fc_plan->nargs * sizeof(Oid));
		memcpy(fc_newplan->argtypes, fc_plan->argtypes, fc_plan->nargs * sizeof(Oid));
	}
	else
		fc_newplan->argtypes = NULL;
	fc_newplan->parserSetup = fc_plan->parserSetup;
	fc_newplan->parserSetupArg = fc_plan->parserSetupArg;

	/* 复制所有plancache条目 */
	foreach(fc_lc, fc_plan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc);
		CachedPlanSource *fc_newsource;

		fc_newsource = CopyCachedPlan(fc_plansource);
		fc_newplan->plancache_list = lappend(fc_newplan->plancache_list, fc_newsource);
	}

	MemoryContextSwitchTo(fc_oldcxt);

	/*
	 * 标记为已保存，将其重新归属到 CacheMemoryContext 下，并将所有
	 * 组件 CachedPlanSources 标记为已保存。这个序列不能在过程中失败，
	 * 因此没有长期内存泄漏的风险。
	 */
	fc_newplan->saved = true;
	MemoryContextSetParent(fc_newplan->plancxt, CacheMemoryContext);

	foreach(fc_lc, fc_newplan->plancache_list)
	{
		CachedPlanSource *fc_plansource = (CachedPlanSource *) lfirst(fc_lc);

		SaveCachedPlan(fc_plansource);
	}

	return fc_newplan;
}

/*
 * 通过名称内部查找短暂的命名关系。
 */
static EphemeralNamedRelation
fc__SPI_find_ENR_by_name(const char *fc_name)
{
	/* 内部静态函数；任何错误都是SPI本身的bug */
	Assert(fc_name != NULL);

	/* 如果没有添加tuplestores，快速退出 */
	if (_SPI_current->queryEnv == NULL)
		return NULL;

	return get_ENR(_SPI_current->queryEnv, fc_name);
}

/*
 * 注册一个短暂的命名关系，以供计划者和执行器在
 * 使用此SPI连接的后续调用中使用。
 */
int SPI_register_relation(EphemeralNamedRelation fc_enr)
{
	EphemeralNamedRelation fc_match;
	int			fc_res;

	if (fc_enr == NULL || fc_enr->md.name == NULL)
		return SPI_ERROR_ARGUMENT;

	fc_res = fc__SPI_begin_call(false);	/* 保持当前内存上下文 */
	if (fc_res < 0)
		return fc_res;

	fc_match = fc__SPI_find_ENR_by_name(fc_enr->md.name);
	if (fc_match)
		fc_res = SPI_ERROR_REL_DUPLICATE;
	else
	{
		if (_SPI_current->queryEnv == NULL)
			_SPI_current->queryEnv = create_queryEnv();

		register_ENR(_SPI_current->queryEnv, fc_enr);
		fc_res = SPI_OK_REL_REGISTER;
	}

	fc__SPI_end_call(false);

	return fc_res;
}

/*
 * 通过名称注销一个短暂的命名关系。这可能是一个
 * 很少使用的功能，因为SPI_finish会自动清除它。
 */
int SPI_unregister_relation(const char *fc_name)
{
	EphemeralNamedRelation fc_match;
	int			fc_res;

	if (fc_name == NULL)
		return SPI_ERROR_ARGUMENT;

	fc_res = fc__SPI_begin_call(false);	/* 保持当前内存上下文 */
	if (fc_res < 0)
		return fc_res;

	fc_match = fc__SPI_find_ENR_by_name(fc_name);
	if (fc_match)
	{
		unregister_ENR(_SPI_current->queryEnv, fc_match->md.name);
		fc_res = SPI_OK_REL_UNREGISTER;
	}
	else
		fc_res = SPI_ERROR_REL_NOT_FOUND;

	fc__SPI_end_call(false);

	return fc_res;
}

/*
 * 使用此SPI连接注册'tdata'中的瞬态关系。
 * 这应该由PL实现的触发处理程序在连接后调用，
 * 以使过渡表对于在该连接中运行的任何查询可见。
 */
int SPI_register_trigger_data(TriggerData *fc_tdata)
{
	if (fc_tdata == NULL)
		return SPI_ERROR_ARGUMENT;

	if (fc_tdata->tg_newtable)
	{
		EphemeralNamedRelation fc_enr =
		palloc(sizeof(EphemeralNamedRelationData));
		int			fc_rc;

		fc_enr->md.name = fc_tdata->tg_trigger->tgnewtable;
		fc_enr->md.reliddesc = fc_tdata->tg_relation->rd_id;
		fc_enr->md.tupdesc = NULL;
		fc_enr->md.enrtype = ENR_NAMED_TUPLESTORE;
		fc_enr->md.enrtuples = tuplestore_tuple_count(fc_tdata->tg_newtable);
		fc_enr->reldata = fc_tdata->tg_newtable;
		fc_rc = SPI_register_relation(fc_enr);
		if (fc_rc != SPI_OK_REL_REGISTER)
			return fc_rc;
	}

	if (fc_tdata->tg_oldtable)
	{
		EphemeralNamedRelation fc_enr =
		palloc(sizeof(EphemeralNamedRelationData));
		int			fc_rc;

		fc_enr->md.name = fc_tdata->tg_trigger->tgoldtable;
		fc_enr->md.reliddesc = fc_tdata->tg_relation->rd_id;
		fc_enr->md.tupdesc = NULL;
		fc_enr->md.enrtype = ENR_NAMED_TUPLESTORE;
		fc_enr->md.enrtuples = tuplestore_tuple_count(fc_tdata->tg_oldtable);
		fc_enr->reldata = fc_tdata->tg_oldtable;
		fc_rc = SPI_register_relation(fc_enr);
		if (fc_rc != SPI_OK_REL_REGISTER)
			return fc_rc;
	}

	return SPI_OK_TD_REGISTER;
}
